0

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

Davood Falahati
  • 1,474
  • 16
  • 34

3 Answers3

1

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.

lawrence-witt
  • 8,094
  • 3
  • 13
  • 32
  • I went through both options you offered. However, it didn't solve my problem. When I use useRef hook, it updates storeMap once and further changes in its value wont be sensed by react. – Davood Falahati Jul 24 '20 at 07:30
  • Correct. As I said, Maps are a mutable store of data, designed to be updated without losing their reference value. This is the opposite of how React decides when to update state variables; by comparing shallow inequality. The best solution I can think of is to set a new Map in the state each time it updates - I've adjusted my answer. Again, I question why you would want to do this as you have now lost many of the benefits of using a Map in the first place. – lawrence-witt Jul 24 '20 at 08:30
  • I have some lists which I don't have prior knowledge of them. Each list has a key and I want to store the selected values of them on a map. Say selectMap = {list1: "option1", list2:"option2 ... }. I want to update selectMap every time user picks an item on each list. That's why I used Map. Please let me know if there is a more insightful way to do so. – Davood Falahati Jul 24 '20 at 09:32
1

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.

Giovanni Esposito
  • 10,696
  • 1
  • 14
  • 30
1

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>;
            })}
        </>
    );
};
Shuki
  • 41
  • 3