Using ES6 Map with React State hooks
State hooks are a great addition to react that allows us to write stateful components concisely without the use of a class component. If you are familiar with React, you know that to use state, you’d have to write a class component —
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}The equivalent of the above using state hook would be as concise as -
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}The setCount method returned by the useState hook works similar to this.setState in the class component. They both pass the updated state to react and enqueue a re-render.
However, there is a difference — In case of setState, React will always re-render the component and it’s children(unless the shouldComponentUpdate lifecycle method returns false). With setCount, React will bail out on an update if the updated state passed is same as previous state. React uses Object.is algorithm to compare the state.
In this article, we will see how this impacts the usage of a ES6 Map(or any other non-primitive as a matter of fact) with the state hook. Consider a function component that uses a Map —
function MapComponent(){
const [myMap, setMyMap] = useState(new Map()); //declaring and initializing state with an empty map
return(
<ul>
{[...myMap.keys()].map(k => (
<li key={k}>myMap.get(k)</li>
))}
</ul>
);
}Note that, to loop over the map inside jsx, we can not use forEach since forEach returns an undefined value. So, we loop over the myMap.keys() which returns an array. Map preserves the order in which key-value pairs are inserted.
The set() method on the map adds or updates the entry and returns the map. So, let’s implement the updateMap method —
function MapComponent(){
const [myMap, setMyMap] = useState(new Map());
const updateMap = (k,v) => {
setMyMap(myMap.set(k,v));
}
return(
<ul>
{[...myMap.keys()].map(k => (
<li key={k}>myMap.get(k)</li>
))}
</ul>
);
}myMap.set() updates the map and setMyMap sets the state and tells React to re-render the component. Except that in this case, React doesn’t trigger a render. Any guesses ?
You’re right. React does a comparison of new state and old state and in this case, it compares the references of new Map to old Map which share the same value. So, React bails out without triggering an update.
What we need to pass to setMyMap is a clone of the map instead of a copy of the reference. A simple change to the setMyMap will do the trick —
function MapComponent(){
const [myMap, setMyMap] = useState(new Map());
const updateMap = (k,v) => {
setMyMap(new Map(myMap.set(k,v)));
}
return(
<ul>
{[...myMap.keys()].map(k => (
<li key={k}>myMap.get(k)</li>
))}
</ul>
);
}




