Error: An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft
Asked Answered
C

7

44

I have my reducer


const userAuthSlice = createSlice({
    name: "userAuth",
    initialState: {
        token: '',
    },
    reducers: {
        setToken: (state, action) => state.token = action.payload.test,
    },
});

And I have my dispatch command

<button
   value={text.sign_in.submit}
   onClick={() => dispatch(userAuthSlice.actions.setToken({test:"test"}))}

/>

As I press the button what I get is this error:

enter image description here

I have isolated everything to be sure that this is the problem and nothing else.

Why does this error pop up?

Cartulary answered 22/3, 2020 at 23:22 Comment(0)
I
88

The issue is the use of an arrow function with no curly braces as the reducer, because that acts as an implicit return statement. So, you're both mutating state.token, and returning the result of the assignment.

Per the Immer docs on returning data, there's a couple ways to fix this:

  • Adding the void operator in front of the assignment
  • Wrapping the assignment in curly braces to make it a function body

so setToken reducer can be updated with void as

setToken: (state, action) => void(state.token = action.payload.test)
Infracostal answered 23/3, 2020 at 15:16 Comment(0)
S
16

Points to learn about IMMER

  1. If you return anything other than draft from the recipe function, that return value will replace the state
  2. You mostly want to mutate the draft to mutate the state, ** so returning draft after modifying it is optional**, the immer will return finalized draft anyway
  3. if you return anything else from recipe function then as per point 1, the new return value will replace the state
  4. Point 3 is ok, only if you do not modify the draft in your recipe function. i.e. you can choose one of the two
    a. modify the draft --> return draft or undefined, either is ok
    b. want to return some new value, then don't touch the draft at all

Now coming to answer for the question. this is your reducer function

(state, action) => state.token = action.payload.test

This function is doing two things,
1. modifying state
2. returning action.payload.test

so it's violating point 4 and hence the error from Immer library

in this specific case, intention is only to modify the state, so we have to undo the existing return with void

setToken: (state, action) => void(state.token = action.payload.test)

However, Immer library recommends the use of code block to insure consistency across large code-base, this code block implicitly returns undefined

setToken: (state, action) => { state.token = action.payload.test } 
Swash answered 13/7, 2020 at 5:41 Comment(0)
G
14

Correct answer

Use Instead of

const userAuthSlice = createSlice({
    name: "userAuth",
    initialState: {
        token: '',
    },
    reducers: {
        setToken: (state, action) => state.token = action.payload.test,
    },
});

Use this {I have just added brackets}

const userAuthSlice = createSlice({
    name: "userAuth",
    initialState: {
        token: '',
    },
    reducers: {
        setToken: (state, action) => { state.token = action.payload.test}, // <<= Change Here
    },
});
Grallatorial answered 15/10, 2021 at 7:7 Comment(0)
V
4

For those who are working with React PullState, my issue was with the syntax:

I had:

 const onUpdate = (value: any) => {
    someStore.update((s) => (s.value = value));
 };

but has to be:

 const onUpdate = (value: any) => {
    someStore.update((s) => {
       s.value = value;
    });
 };

Check that I sorrounded the declarations with brackers {}

For some reason, doesn't like the arrow function declared on the first approach.

Versify answered 4/3, 2022 at 14:18 Comment(0)
M
0

Instead of

 state.token = action.payload.token

use

 const token = action.payload.token
 state.token = token

Constants are immutable, and assigning action.payload contents to a constant resolved a problem for me (Note that in case the payload is an object it is not necessary

Working example of createSlice:

import { createSlice } from '@reduxjs/toolkit';

export const slice = createSlice({
  name: 'login',
  initialState: {
      username: "Foo",
      password: "AAA",
      authorized : false
  },
  reducers: {
    setUsername: (state, action) => {

      const value = action.payload
      state.username = value // action.payload.username

    },
    setPassword: (state, action) => {

      const value = action.payload
      state.password = value // action.payload.password

    },
    logon: (state, action) => {

      state.username = action.payload.username
      state.password = action.payload.password
      state.authorized = true

    },
    logoff: state => state.authorized = false
  },
});
Mercurialism answered 5/4, 2020 at 19:58 Comment(0)
A
0

You should remove return statement only and .test

Aerostation answered 11/9, 2022 at 19:24 Comment(0)
P
-4

you may forget break; statement at your reducer.js file;

 case LOGIN_USER_ERROR_RESET:
        draft.errormsg = '';
        draft.loading = false;
        draft.error = false;
        break;
Placer answered 15/6, 2020 at 13:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.