How to type assignment using Object.keys in TypeScript

56 views Asked by At

Following is a store using Valtio, That thing works as expected but need way to type-hint this. The goal is to create store that will hold an object & allows to update any value/ values by using action method, while not requiring to define action methods for every use case.

I have a problem with the type check in the below code:

import { proxy } from 'valtio';

type PaymentStore = {
  loading: boolean;
  token: string | null;
  id: string | null;
  initialized: boolean;

  total: number;
  processing: boolean;
  paid: boolean;

  isError: boolean;
  errMsg: null | string;
};

const defaultValues: PaymentStore = {
  loading: false,
  token: null,
  id: null,
  initialized: false,

  total: 0,
  processing: false,
  paid: false,

  isError: false,
  errMsg: null,
};

const paymentStore = proxy<PaymentStore>(defaultValues);

const paymentActions = {
  setProperty<T extends keyof PaymentStore>(key: T, value: PaymentStore[T]) {
    paymentStore[key] = value
  },

  setProperties<T extends Partial<{[k in keyof PaymentStore]: PaymentStore[k]}>>(values: T) {
    Object.keys(values).forEach((valueKey) => {
      // @ts-ignore
      paymentStore[valueKey] = values[valueKey]; // >>> how to type this line?
    })
  }
}

// getting excellent aut-complete in following methods
paymentActions.setProperty('loading', false);
paymentActions.setProperties({
  loading: true,
  isError: true,
});

1

There are 1 answers

2
Nick Vu On
paymentStore[valueKey] = values[valueKey];

You have 2 problems with the above line of code:

  1. paymentStore[valueKey] is a dynamic value, so we don't know the type of that field beforehand with PaymentStore only. You have to assign paymentStore variable with Record<keyof PaymentStore, unknown>. unknown in this context is to tell the type check that you don't know the type for now but you know the key is a part of PaymentStore
  2. typedKey in values[typedKey] is a string because of forEach params. Therefore, you can not match keyof PaymentStore with string. You have to assert that type with as keyof PaymentStore

You can try the below approach with some comments in the code

import { proxy } from 'valtio';

type PaymentStore = {
  loading: boolean;
  token: string | null;
  id: string | null;
  initialized: boolean;

  total: number;
  processing: boolean;
  paid: boolean;

  isError: boolean;
  errMsg: null | string;
};

const defaultValues: PaymentStore = {
  loading: false,
  token: null,
  id: null,
  initialized: false,

  total: 0,
  processing: false,
  paid: false,

  isError: false,
  errMsg: null,
};

//make sure the key is a part of PaymentStore and the value can be unknown because we cannot determine the type before the value assignment
const paymentStore: Record<keyof PaymentStore, unknown> = proxy<PaymentStore>(defaultValues);

const paymentActions = {
  setProperty<T extends keyof PaymentStore>(key: T, value: PaymentStore[T]) {
    paymentStore[key] = value
  },

  setProperties<T extends Partial<PaymentStore>>(values: T) {
    Object.keys(values).forEach((valueKey) => {
      //valueKey type is a string by default due to `forEach`, so we need to assert that type for PaymentStore
      const typedKey = valueKey as keyof PaymentStore
      paymentStore[typedKey] = values[typedKey];
    })
  }
}

// getting excellent aut-complete in following methods
paymentActions.setProperty('loading', false);
paymentActions.setProperties({
  loading: true,
  isError: true,
});
//test case
paymentActions.setProperties({
  loading: 'test', //not working because loading is a boolean
  total: 'test', //not working because total is a number
  isError: true,
});

Playground link