最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Redux + Typescript -> Error: A case reducer on a non-draftable value must not return undefined redux - Stack

programmeradmin2浏览0评论

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 be updateMode: (...) => ... – 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
Add a ment  | 

4 Answers 4

Reset to default 6

I'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,
  ...,
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论