Search code examples
typescriptimmer.js

How to update a nested property which type is generic from a immer produce function?


I'd like to use immer in my react typescript application.

One of my reducer should deal with a state with a generic argument.

The generic argument can be anything (basic type, array, object, ...)

How to update such property within a produce method ?

Here's a minimal repro code:

import { produce } from 'immer';

type SomeState<TResult> = {
    inner: TResult
}

type SetInner<TResult> = {
    type: 'SET_INNER';
    newValue: TResult;
}

const reducer = <TResult>(
    state: SomeState<TResult>,
    action: SetInner<TResult>
): SomeState<TResult> => {

    return produce(state, draft => {
        switch (action.type) {
            case 'SET_INNER':
                draft.inner = action.newValue;
                break;
        }
    });

}

This code fails on the line draft.inner = action.newValue; with this error:

Type 'TResult' is not assignable to type 'Draft'.

How to fix that ?

To clarify, I'd like these use cases to be possible:

// Basic type
const initialState = {
    inner: "foo"
};

const action: SetInner<string> = {
    type: 'SET_INNER',
    newValue: "bar"
}

const newState = reducer(initialState, action);
console.log(newState);

// Object type
type Point = { x: number, y: number }
const initialState2 = {
    inner: { x: 10, y: 4 }
};

const action2: SetInner<Point> = {
    type: 'SET_INNER',
    newValue: { x: -5, y: 14 }
}

const newState2 = reducer(initialState2, action2);
console.log(newState2);

Solution

  • I think answer of @captain-yossarian from Ukraine should be corrected.

    The behavior below is considered harmful since you are introducing an implementation detail here and some cases it won't work...

    TResult -> Draft<TResult>
    

    castDraft is the no-op utility function for this case.

    //old one
    draft.inner = action.newValue
    
    //new one
    draft.inner = castDraft(action.newValue)
    

    For further reading: https://immerjs.github.io/immer/typescript#cast-utilities