Map in React state
- Published on
- Authors
- Name
- Brian Kimball
Client Side Data State
For many years, I have held fetched data in state as an array. This does not have to be React state, you could be using react-query or swr or some other data fetching library. Normally, I reach for tanstack-query (react-query), but for simplification we will just use React state. I build applications that utilize web sockets or server sent events for real-time updates. A lot of times we listen to an event and update the client state based on that event.
An example of using an array to hold state:
// react component
function ListComponent() {
// our data state
const [items, setItems] = useState([])
useEffect(() => {
// fetch data and add to state
socket
.service('items')
.find()
.then(({ data }) => {
setItems(data)
})
}, [])
useEffect(() => {
socket.service('items').on('created', (item) => {
// on created append new item to array
setItems([...items, item])
})
socket.service('items').on('patched', (item) => {
// on patched map through items and replace item with the same id
setItems(items.map((t) => (t.id === item.id ? item : t)))
})
socket.service('items').on('removed', (item) => {
// on removed filter out the entry with the same id
setItems(items.filter((t) => t.id !== item.id))
})
})
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}
There is nothing inherently wrong with the code above. Maybe it's a bit verbose, but it should be pretty clear what it does and what is happening. It certainly works, but it feels a bit awkward to map the entire array on a "patched" event or filter on a "removed" event.
In ES6 the Map object was introduced. You can read the details on the Mozilla Docs. A Map will hold key/value pairs. You can use your "id" field as a key, and the rest of the item as a value in the pairing. Below is the same example as above, except we are using a Map instead of an array.
An example of using a Map to hold state:
// react component
function ListComponent() {
// our data state
const [items, setItems] = useState(new Map())
useEffect(() => {
// fetch data and add to state
socket
.service('items')
.find()
.then(({ data }) => {
// use the id as the key and the full item as the value
setItems(new Map(data.map((d) => [d.id, d])))
})
}, [])
useEffect(() => {
// listen for each event
['created', 'patched', 'removed'].forEach((event) => {
socket.service('items').on(event, (item) => {
if (event === 'removed') {
// if removed, delete the key from the map
items.delete(item.id)
} else {
// created or patched scenarios we just set the appropriate key
items.set(item.id, item)
}
// set state with a new map
setItems(new Map(items))
})
})
})
return (
<ul>
{Array.from(items).map(([id, item]) => (
<li key={id}>{item.name}</li>
))}
</ul>
)
}
The trick here is you have to create a new Map when the state is updated. So far, I like using this solution. It seems to simplify the code. I don't have to map or filter the entire array to update or remove an entry. If you have any questions, comments or improvements, reach out to me on twitter