I'm trying to import typescript in my redux application. I use redux toolkit createSlice function like this:
const slice = createSlice({
name: sliceName,
initialState,
reducers: {
updateMode(state: SliceState, action: PayloadAction<ModePayload>) {
state = action.payload.mode;
},
},
});
When I dispatch a updateMode action, I receive the following error:
Error: A case reducer on a non-draftable value must not return undefined
But my action.payload is well defined and redux toolkit is up to date. It have something to do with my code but I can't figure it out...
I tried to return state in updateMode and it works, but I get a TypeScript error:
Type '(state: SliceState, action: { payload: ModePayload; type: string; }) => SliceState' is not assignable to type 'CaseReducer<"SHOW_LIST", { payload: any; type: string; }> | CaseReducerWithPrepare<"SHOW_LIST", PayloadAction<any, string, any, any>>'.
Type '(state: SliceState, action: { payload: ModePayload; type: string; }) => SliceState' is not assignable to type 'CaseReducer<"SHOW_LIST", { payload: any; type: string; }>'.
Type 'SliceState' is not assignable to type 'void | "SHOW_LIST"'.
Type '"SHOW_SKILL"' is not assignable to type 'void | "SHOW_LIST"'
Can someone explain to me what am I doing wrong ?
Below my full code:
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
type SliceState = "SHOW_LIST" | "SHOW_SKILL";
let initialState: SliceState = "SHOW_LIST";
const sliceName = "mode";
interface ModePayload {
mode: SliceState;
}
//--- Slice reducer ---
const modeSlice = createSlice({
name: sliceName,
initialState,
reducers: {
updateMode(state: SliceState, action: PayloadAction<ModePayload>) {
return (state = action.payload.mode);
},
},
});
export const { updateMode } = modeSlice.actions;
export default modeSlice.reducer;
I'm trying to import typescript in my redux application. I use redux toolkit createSlice function like this:
const slice = createSlice({
name: sliceName,
initialState,
reducers: {
updateMode(state: SliceState, action: PayloadAction<ModePayload>) {
state = action.payload.mode;
},
},
});
When I dispatch a updateMode action, I receive the following error:
Error: A case reducer on a non-draftable value must not return undefined
But my action.payload is well defined and redux toolkit is up to date. It have something to do with my code but I can't figure it out...
I tried to return state in updateMode and it works, but I get a TypeScript error:
Type '(state: SliceState, action: { payload: ModePayload; type: string; }) => SliceState' is not assignable to type 'CaseReducer<"SHOW_LIST", { payload: any; type: string; }> | CaseReducerWithPrepare<"SHOW_LIST", PayloadAction<any, string, any, any>>'.
Type '(state: SliceState, action: { payload: ModePayload; type: string; }) => SliceState' is not assignable to type 'CaseReducer<"SHOW_LIST", { payload: any; type: string; }>'.
Type 'SliceState' is not assignable to type 'void | "SHOW_LIST"'.
Type '"SHOW_SKILL"' is not assignable to type 'void | "SHOW_LIST"'
Can someone explain to me what am I doing wrong ?
Below my full code:
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
type SliceState = "SHOW_LIST" | "SHOW_SKILL";
let initialState: SliceState = "SHOW_LIST";
const sliceName = "mode";
interface ModePayload {
mode: SliceState;
}
//--- Slice reducer ---
const modeSlice = createSlice({
name: sliceName,
initialState,
reducers: {
updateMode(state: SliceState, action: PayloadAction<ModePayload>) {
return (state = action.payload.mode);
},
},
});
export const { updateMode } = modeSlice.actions;
export default modeSlice.reducer;
Share
Improve this question
edited Jul 29, 2020 at 15:07
Emmanuel Madarassou
asked Jul 29, 2020 at 15:01
Emmanuel MadarassouEmmanuel Madarassou
1193 silver badges13 bronze badges
5
-
My guess is you didn't understand very well how to define a slice and what is the state or
initialState
in this case. – Second Son Commented Jul 29, 2020 at 15:14 -
Have you checked this link?; it seems
updateMode(...)
should beupdateMode: (...) => ...
– Dane Brouwer Commented Jul 29, 2020 at 15:17 - @DaneBrouwer Yes I've tried both syntaxes with the same result. – Emmanuel Madarassou Commented Jul 29, 2020 at 15:25
- @SecondSon Surely you might be right. That's why I've ask this question, I didn't find any answer in redux docs. – Emmanuel Madarassou Commented Jul 29, 2020 at 15:28
- @EmmanuelMadarassou my point is if you understand what is a state, as redux docs explains it then writing a slice might be less confusing as to what is a state and how to update it. – Second Son Commented Jul 29, 2020 at 15:34
4 Answers
Reset to default 6I'm a Redux maintainer and the creator of Redux Toolkit.
The actual problem is with this line:
state = action.payload.mode;
This doesn't actually acplish anything useful. Immer works by tracking mutations to an existing value, like state.someField = 123
. The line you wrote only changes the local state
variable to point to something else instead.
Immer requires that you either return an entirely new state value, or mutate the existing state. That line of code does neither. Instead, the reducer only ends up returning undefined
, thus the error.
If you want to replace the existing state entirely, you should just do return action.payload.mode
.
Also, as a side note: if initialState
is correctly typed, you don't have to supply a type for state
for each reducer, as it will be inferred.
Following the document, you might have to cast your type as following:
const modeSlice = createSlice({
name: sliceName,
initialState: initialState as SliceState,
reducers: {
updateMode(state: SliceState, action: PayloadAction<ModePayload>) {
return (state = action.payload.mode);
},
},
});
Typically you would have something like this
type SliceState = {
mode: string;
};
interface ModePayload {
mode: string;
}
const initialState: SliceState = {
mode: 'initial mode',
};
//--- Slice reducer ---
const modeSlice = createSlice({
name: 'modeSlice',
initialState,
reducers: {
updateMode(state, action: PayloadAction<ModePayload>) {
state.mode = action.payload.mode;
},
},
});
export const { actions, reducer, name: sliceKey } = modeSlice;
And then wherever you are importing this slice and you have to dispacth your action you will then have something like.
import {actions} from '../mode-slice'
....
// somewhere in your ponent/view or whatever
dispatch(actions.updateMode({mode: 'new mode'}))
Essentially your slice holds a slice of your global state stored by redux and you are slicing down and manipulating just a slice of it. The slice contains both the reducer(s) and the action(s) that allow you to manipulate your state(slice of the state).
React Boilerplate CRA has some good explanations about it right here.
Thanks everyone for your answers.
I merged both your answers and I get the following result wich does what I wanted:
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
type SliceState = "SHOW_LIST" | "SHOW_SKILL";
let initialState: SliceState = "SHOW_LIST";
const sliceName = "mode";
interface ModePayload {
mode: SliceState;
}
//--- Slice reducer ---
const modeSlice = createSlice({
name: sliceName,
initialState: initialState as SliceState,
reducers: {
updateMode(state, action: PayloadAction<ModePayload>) {
return action.payload.mode;
},
},
});
export const { updateMode } = modeSlice.actions;
export default modeSlice.reducer;
This allows me to have my mode slice in my global state equal "SHOW_LIST" || "SHOW_SKILL" which is what I wanted in the first place. If I need more data for my mode, I will surely make my slice state like this:
{
mode: "SHOW_LIST",
otherAttribute: otherValue,
...,
}