Using generic parameter in Redux Toolkit createSlice reducer with prepare?
Asked Answered
S

4

6

I'm trying to create a reducer in RTK that takes two arguments which share a generic type. However it seems impossible to share the generic type between the reducer and prepare functions.

Here is an example of what I'm trying to achieve

type SetPayload<T> = {
    thingToBeSet: MyClass<T>;
    value: T;
}

export const mySlice = createSlice({
    name: 'mySlice',
    initialState,
    reducers: {
        myReducer: {
            reducer: (state, action: PayloadAction<SetPayload<any>>) => {
                // Modify state here
            },
            prepare: <T>(thingToBeSet: MyClass<T>, value: T) => {
                return {
                    payload: { thingToBeSet, value }
                }
            }
        }
    }
});

As shown in the code above, my prepare function uses a generic parameter (as the type of some properties on thingToBeSet must match the type of value). However, this type parameter cannot be used for the type of action in the reducer function - I tried setting this to any which gives this error:

The types of 'payload.thingToBeSet' are incompatible between these types.
        Type 'MyClass<never>' is not assignable to type 'MyClass<any>'.
          Type 'any' is not assignable to type 'never'

As any cannot be converted to T - But how can I share T between both the reducer and prepare functions here?

What I think I want is to somehow create a generic object literal for the myReducer object, something like this (unfortunately not valid TS - is this possible?):

myReducer: <T> {
    reducer: (state, action: PayloadAction<SetPayload<T>>) => {
        // Modify state here
    },
    prepare: (thingToBeSet: MyClass<T>, value: T) => {
        return {
            payload: { thingToBeSet, value }
        }
    }
}

Any help will be greatly appreciated.

Sylvia answered 31/1, 2022 at 17:54 Comment(0)
R
3

Unfortunately that is not possible. Those types are mapped internally to create a new action creator function - and mapped types cannot inherit generic functionality.

The best you could do would be to cast that action generator to your generic function type before you export it.

export const myReducer = slice.actions.myReducer as <T>(thingToBeSet: MyClass<T>, value: T) => PayloadAction<SetPayload<T>>)
Redhead answered 31/1, 2022 at 19:2 Comment(0)
S
2

The eventual solution I used for this was to create the action manually outside of createSlice, and use extraReducers property for the reducer. Setting the type property on the function allows for automatic typing and similar usage to RTK. This is more verbose and not ideal, but it at least gives the correct typing, and implicit types in the reducer.

Action:

const myActionName = 'myAction';
export function myAction<T>(thingToBeSet: MyClass<T>, value: T): PayloadAction<SetPayload<T>> {
    return {
        type: myActionName,
        payload: { thingToBeSet, value }
    }
}
myAction.type = myActionName;

Reducer:

createSlice({
    ...
    extraReducers: (builder) => {
        builder
            .addCase(myAction, (state, action) => {
                // Perform state changes
                // `state` and `action` have correct implicit types
                // (`action` is of type `PayloadAction<SetPayload<unknown>>`)
            })
    }
});
Sylvia answered 1/2, 2022 at 17:38 Comment(0)
A
0

What I did was create a helper custom hook, so I dispatch the actions through it. With that I was able to pass generic parameters.

const useProducerForm = () => {
  const dispatch = useAppDispatch();

  const setFormValue = <T extends DefaultForms>(
    form: T,
    value: ProducerFormState['data'][T]['value'],
  ) =>
    dispatch(
      setFormValueAction({
        form,
        value,
      }),
    );

  return {
    setFormValue,
  };
};
Argil answered 5/5, 2023 at 16:48 Comment(0)
E
-1

Any One using redux toolkit and he wants to update the state without creating reducer each time if you wana update the state , rather reducers will be created ounce by the wrapper function and one can update the state like below

const {inventoryState, inventoryActions} = inventoryStore()

useEffect(() => {
  inventoryActions.item.name.set("ITEM 101"); 
}, [state])
  • No explicit reducer , reducer will be created implicitly
  • Granular level updates
  • When set is called it will dispatch those to store automatically
  • Redux toolkit gives logs like InventorySlice/item.name
  • link to follow : https://mohsin-ejaz.gitbook.io/redux/redux-easy
Emersed answered 9/6, 2023 at 10:54 Comment(2)
Please if you downgrade please give reason otherwise donot downgrade any postEmersed
Hi there @Mohsin Ejaz. Your post in not answering the question at all. He is asking about how to share the generic type between the reducer and prepare functions and you are explaining how to update the state without dispatching an action. You are not using generics in your code.Heptastich

© 2022 - 2024 — McMap. All rights reserved.