Davood
Davood

Reputation: 1566

Reactjs hooks: update value of map

I'm new to reactjs and that's why it comes naive to you.

I need to update the value of a map in which its keys are unknown.

  const App = () => {
    const [storeMap, setStoreMap] = useState(new Map());
    let _tmpMap = new Map();
    return (<>
    {Object.keys({ key1: "hey", key2: "you" }).map((item) => {
          return (
            <button
              value={item}
              key={item}
              onClick={(e) => {
                _tmpMap.set(item, e.target.value);
           
                console.log(..._tmpMap); // {1}
                setStoreMap(_tmpMap);
              }}
            >
              {item}
            </button>
          );
          //   return <i key={item}>KJ </i>;
        })}
  </>)
   
  }

What I am expecting to see in the above code after clicking both buttons is:

  /*    {1}    */ 
  console.log(..._tmpMap)
  //i expect this: {key1:"key1" , key2:"key2"}

What I see in reality is {key1:"key1"} after pressing key 1 and { key2:"key2"} after pressing key 2

My question is:

How can I update storeMap while preserving its previous entries?

Here is the code

Upvotes: 0

Views: 2388

Answers (3)

Shuki
Shuki

Reputation: 41

storeMap.set() updates the map and setStoreMap sets the state.

React compares the references of the new and old Map which, in this case, share the same value. If you want React to "know" about the update you will need to pass to setStoreMap a clone of the Map instead of a copy of the old reference, you can do that by creating a new Map. I believe you can drop the use of _tmpMap.

const App = () => {
    const [storeMap, setStoreMap] = useState(new Map());

    const updateStoreMap = (k, v) => {
        // pass a clone of storeMap
        setStoreMap(new Map(storeMap.set(k, v)));
    };
    
    return (
        <>
            {Object.keys({ key1: 'hey', key2: 'you' }).map((item) => {
                return (
                    <button
                        value={item}
                        key={item}
                        onClick={(e) => {
                            updateStoreMap(item, e.target.value);
                        }}
                    >
                        {item}
                    </button>
                );
                //   return <i key={item}>KJ </i>;
            })}
        </>
    );
};

Upvotes: 1

lawrence-witt
lawrence-witt

Reputation: 9354

When you call setStoreMap, the component rerenders and _tmpMap evaluates to a new Map again. The Map you updated belongs to the previous render and cannot be accessed. Anything you wish to preserve between renders has to be in state or a ref, so, you could do something like this:

const App = () => {
    const [storeMap, setStoreMap] = useState(new Map());
    let _tmpMap = useRef(new Map());
    return (<>
    {Object.keys({ key1: "hey", key2: "you" }).map((item) => {
          return (
            <button
              value={item}
              key={item}
              onClick={(e) => {
                _tmpMap.current.set(item, e.target.value);
                setStoreMap(new Map(_tmpMap.current));
              }}
            >
              {item}
            </button>
          );
          //   return <i key={item}>KJ </i>;
        })}
  </>)   
}

However, it's generally advised not to use Maps with React as they are mutable, and React will have no way of knowing when one is mutated. The only way that storeMap will trigger rerenders and effects is if you set it to a new Map every time you update it. If you absolutely must use Maps, then mutable refs are the closest thing to them that React offers. An Object is about the most complex thing that belongs in React state. See this thread.

Upvotes: 1

Giovanni Esposito
Giovanni Esposito

Reputation: 11156

Ciao, here working code. If you click key1 or key2 button, elements are added to the Map, if you click show result button you will see storeMap value.

Upvotes: 1

Related Questions