Home > Software engineering >  How to update a nested property which type is generic from a immer produce function?
How to update a nested property which type is generic from a immer produce function?

Time:01-13

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

CodePudding user response:

Please consider this example:

import { produce, Draft } from 'immer';

type SomeState<TResult> = {
    inner: TResult
}

type SetInner<TResult> = {
    type: 'SET_INNER';
    newValue: Draft<TResult>; // <--------------- CHANGE IS HERE
}

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; // ok
                break;
            }


        }
    });

}

// 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);

Playground

I have wrapped newValue into Draft type. According to source code, Draft states for:

/** Convert a readonly type into a mutable type, if possible */
type Draft<T>=...
  • Related