A case reducer on a non-draftable value must not return undefined

Viewed 7556

I'm working on my project @reduxjs/toolkit and created UserSlice like below.

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { User } from "../../models";

export type UserState = User | null;

export const initialUserState: UserState = null;

const UserSlice = createSlice({
  name: "User",
  initialState: initialUserState,
  reducers: {
    receiveUser: (state: UserState, action: PayloadAction<UserState>) => {
      state = action.payload;
    },
    clearUser: (state: UserState, _action: PayloadAction<void>) => {
      state = null;
    },
    signIn: (
      _,
      _action: PayloadAction<{
        email: string;
        password: string;
      }>
    ) => {},
    signInWithToken: (_, _action: PayloadAction<void>) => {},
  },
});

export default UserSlice;

signIn signInWithToken are action creators just for taking actions in UserSaga module, so they do nothing.

When I dispatch signIn action, below error occurs.

index.js:1 Error: A case reducer on a non-draftable value must not return undefined
    at createReducer.ts:163
    at Array.reduce (<anonymous>)
    at createReducer.ts:143
...

This code snippets pattern works fine my other projects, but this project. I can't figure out the reason of this error, so I'm asking this question. Are there any faults in my code?

4 Answers

Redux Toolkit's createReducer() allows writing reducers that directly mutate the state. This works by wrapping the reducer call with produce from the Immer library.

However, the reducer call isn't wrapped with produce when the current state isn't "draftable" by Immer, which is the case for primitive values, including null:

} else if (!isDraftable(previousState)) {
  // If state is not draftable (ex: a primitive, such as 0), we want to directly
  // return the caseReducer func and not wrap it with produce.
  const result = caseReducer(previousState as any, action)

  if (typeof result === 'undefined') {
    throw Error(
      'A case reducer on a non-draftable value must not return undefined'
    )
  }

  return result
}

Because your initial User state is null, you must return the new state from your receiveUser() reducer.

I believe you're not returning the state in all of your reducers, because when using arrow functions objects need additional round brackets:

  reducers: {
    receiveUser: (state: UserState, action: PayloadAction<UserState>) => ({
      state: action.payload;
    }),
    clearUser: (state: UserState, _action: PayloadAction<void>) => ({
      state : null;
    }),
    signIn: (
      _,
      _action: PayloadAction<{
        email: string;
        password: string;
      }>
    ) => ({}),
    signInWithToken: (_, _action: PayloadAction<void>) => ({}),
  },

The arrow function has two syntax forms:

  • (param1, param2, …, paramN) => { statements }
  • (param1, param2, …, paramN) => expression

The first one does not necessarily return a value unless it contains a return statement, but the second will return a value. You are using the first form without a return inside, so it won't return anything.

Since the rule is that a case reducer on a non-draftable value must not return undefined, and you can parenthesize the body of a function to return an object literal expression like this:

params => ({foo: bar})

You can rewrite your code to this:

reducers: {
  receiveUser: (state: UserState, action: PayloadAction<UserState>) => ({
    state: action.payload;
  }),
  clearUser: (state: UserState, _action: PayloadAction<void>) => ({
    state : null;
  }),
  signIn: (
    _,
    _action: PayloadAction<{
      email: string;
      password: string;
    }>
  ) => ({}),
  signInWithToken: (_, _action: PayloadAction<void>) => ({}),
},

There are two possible issues:

  1. You probably just need to bump your version of @reduxjs/toolkit to the latest version by npm i @reduxjs/toolkit@latest or yarn add @reduxjs/toolkit@latest.

There was a version that had a bug where if you initialized state with null, this could potentially happen. If you install the latest and there is still an issue, please let me know which version you're on and I'll try to reproduce it.

  1. The other possible issue is that if your action.payload in receiveUser is actually undefined, you'll get that error as you shouldn't ever be returning undefined.
Related