3

I am trying to use map to store state, while the state is updating, the components are not re-rendering with the new state

const store = (set, get) => ({
    items: new Map(),
    addItem: (key, item) => {
        set((state) => state.items.set(key, item));
        // this prints the latest state
        console.log(get().items)
    },
    removeItem: (key) => {
        set((state) => state.items.delete(key));
    },
)}
const useStore = create(store);
export default useStore

useEffect doesn't prints anything

const items = useStore((state) => state.items))
useEffect(() => {
   console.log(items)
}, [items]);

3 Answers3

4

You should create a new instance of Map when you update like so:

const store = (set, get) => ({
    items: new Map(),
    addItem: (key, item) => {
        set((state) => {
            const updatedItems = new Map(state.items)
            updatedItems.set(key, item)
            return { items: updatedItems }
        });
        // this prints the latest state
        console.log(get().items)
    },
)}

Note: you'll have to do something similar when you remove items.

thedude
  • 9,388
  • 1
  • 29
  • 30
  • don't you think creating a new map and copying previous data for every operation would hinder the purpose of using map ? – Bharat keswani Aug 03 '22 at 08:16
  • 1
    as long as you tie your `useEffect` to the `items` value it will not rerun at the reference of `items` did not change. – thedude Aug 03 '22 at 10:00
2

If you don't want to create a new instance of a map so that zustand registers state change then change you can use immer.

import create from 'zustand'
import { immer } from 'zustand/middleware/immer'

const store =immer( (set, get) => ({
    items: new Map(),
    addItem: (key, item) => {
        set((state) => state.items.set(key, item));
        // this prints the latest state
        console.log(get().items)
    },
    removeItem: (key) => {
        set((state) => state.items.delete(key));
    }),
)}
const useStore = create(store);
export default useStore
1

The Map instance is always the same. So Zustand cannot detect any changes. You can use an array:

const store = (set, get) => ({
  items: [],
  addItem: (key, item) => {
    set((state) => ({
      items: [...state.items, { key, item }]
    }));
  },
  removeItem: (key) => {
    set((state) => ({
      items: state.items.filter((item) => item.key !== key)
    }));
  }
});
Ivan Shumilin
  • 1,743
  • 3
  • 13
  • 18
  • can't use an array, because of the high frequencey of updates, any other work around ? – Bharat keswani Aug 02 '22 at 06:32
  • @Bharatkeswani Could you provide an example which demonstrate the performance problems? I guess the bottleneck is at the rendering stage rather than at the array operations. – Ivan Shumilin Aug 02 '22 at 07:33
  • 1
    Think of a data table with 100+ rows per page(multi page paginated data) where each row can individually be selected(using checkbox) and all the rows can be selected using a top header checkbox, the cost of doing such operation with map is amortized constant per row, while it takes linear time with an array. – Bharat keswani Aug 03 '22 at 07:33