kokiqn
kokiqn

Reputation: 1

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.

Upvotes: 0

Views: 52

Answers (1)

jsejcksn
jsejcksn

Reputation: 33796

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;
}

Upvotes: 0

Related Questions