Can a redux-toolkit createSlice use a js Map as state?
Asked Answered
J

2

16

In general, using a mutable object such as Map is strongly discouraged.

However, the magic of immer allows immutable objects to be manipulated as though they are mutable.

Specifically, immer supports immutable versions of Map using enableMapSet

In redux-toolkit createReducer and createSlice wrap state manipulation with immer's produce.

Collectively, I think these facts mean code like this should be safe:

import { createSlice } from '@reduxjs/toolkit'

export const testmapSlice = createSlice({
  name: 'testMap',
  // Using a Map() as redux state
  initialState: new Map(),
  reducers: {
    add: (state, action) => {
      state.set(action.payload.identity, action.payload)
    },
  },
})

However, when I use this in a React component, I get the polite error message A non-serializable value was detected in the state, in the path: `testMap`. Value: Map(1) {"A" => {…}} Take a look at the reducer(s) handling this action type: testMap/add..

Is there a way to safely use Map without getting this error message?

Jodoin answered 22/7, 2020 at 15:10 Comment(0)
B
10

Define "safely" :)

In theory, you could put anything you want into the Redux store.

In practice, per that FAQ, non-serializable values are likely to cause things like the DevTools to break (which defeats much of the purpose of using Redux in the first place). Use of Maps and other mutable instances are also likely to cause portions of your UI to not re-render correctly, because React-Redux relies on reference checks to determine if data has changed. So, we specifically tell users that you should never put non-serializable values in the Redux state.

In this particular case, you should be able to use a plain JS object as a lookup table rather than a Map, and accomplish the same behavior.

As an absolute last resort, you can turn off the serialization checking for certain parts of the state, but we strongly discourage folks from doing that.

Birdsong answered 22/7, 2020 at 16:8 Comment(3)
I'm specifically curious about whether immer changes anything. If I was using immutableJS, I could set the initial value to a Map and know it was immutable and that redux is happy. But with immer being the redux-toolkit default, I wondered if there's a way to tell redux, "this is frozen by immer" in the initial state. For my use case, the insertion order iteration of map is desirable, but the number of keys is low, so I'll probably use an array instead.Jodoin
Tbh I don't know exactly how Immer implements support for updating Maps. My assumption is that it doesn't copy the maps, which would lead to bugs if you try to read state => state.some.map in the UI - it would never know to re-render. Note that JS objects do mostly use insertion order for iteration of string keys, which may be sufficient for your purposes.Birdsong
Immer does kind of specifically support Map and Set, although I think it is pretty slow with copying. You also have to specifically enable it. immerjs.github.io/immer/map-setErasmo
C
0

I think it is not safe to use Map() on the state because Redux is already designed to avoid the mutation at the level of Reducers, and when you use createSlice() it even takes care of it in the background. Your idea of a double security at the State level may appear to be another issue that you provoke. It might either provoke the UI not to update. or throw an error (Ps: This is purely analogic. I have not tried it)

Culet answered 15/7, 2021 at 3:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.