Search code examples
typescripttype-inference

TypeScript infer value type based on a passed key


I am encountering a problem with the typization of a reducer function that aims to update any given property of the userData stored in the slice. It takes an array with the key and a value, where the key should be of type TUserData, and the value - of the specified type for the respective key in TUserData.

Here is the reducer:

updateUserProp(state, action: { payload: [keyof TUserData, TUserData[keyof TUserData]]; type: string }) {
    const [key, value] = action.payload;

    state.userData![key] = value;
},

And here is the TUserData type

type TUserData = {
    email: string;
    uid: string;
    username: string;
    firstName: string;
    lastName: string;
    phoneNumber: string;
    createdOn: string;
    initials?: string;
    status: EUserStatus;
    profilePhoto?: string;
    chats?: { [x: string]: EChatStatus };
    messagesSent?: { [x: string]: boolean };
}

The error

Type 'string | { [x: string]: EChatStatus; } | { [x: string]: boolean; } | undefined' is not assignable to type 'string & EUserStatus & (WritableDraft<{ [x: string]: EChatStatus; }> | undefined) & (WritableDraft<{ [x: string]: boolean; }> | undefined)'. Type 'undefined' is not assignable to type 'string & EUserStatus & (WritableDraft<{ [x: string]: EChatStatus; }> | undefined) & (WritableDraft<{ [x: string]: boolean; }> | undefined)'.

is from this line of codea state.userData![key] = value;

TS playground

EDIT

I resolved the error with this solution

function updateUserProp<K extends keyof TUserData>(
    state: { userData?: TUserData },
    action: {
        payload: [K, TUserData[K]];
        type: string;
    },
) {
    const [key, value] = action.payload;
    state.userData![key] = value;
}

Working solution playground

The function works fine on its own. But when used as a reducer, it allows a value of different type (as long as it's among the valid types for the entire object) to be assigned to the key.

dispatch(authActions.updateUserProp(["status", undefined])) doesn't produce an error, but updateUserProp({}, { payload: ["status", undefined]}) (ran from the playground) does.


Solution

  • You can use a generic type parameter constrained by keyof TUserData so that the compiler can infer the specific key as shown below:

    TS Playground

    function updateUserProp<K extends keyof TUserData>(
      state: { userData?: TUserData },
      action: {
        payload: [K, TUserData[K]];
        type: string;
      },
    ) {
      const [key, value] = action.payload;
      state.userData![key] = value;
    }