::
FIG 2.1 // ENTRY
Back to Index

Map in React state

Date:

Client Side Data State

For years, I stored fetched data in arrays—whether using React state, React Query, or SWR. While fetching libraries like TanStack Query are my go-to, let’s look at a simple useState example. In applications using WebSockets or Server-Sent Events (SSE), we often need to update this state in real-time based on incoming events.

Using an Array:

// react component
function ListComponent() {
  const [items, setItems] = useState([])

  useEffect(() => {
    // Initial fetch
    socket
      .service('items')
      .find()
      .then(({ data }) => setItems(data))
  }, [])

  useEffect(() => {
    // Listeners
    socket.service('items').on('created', (item) => {
      setItems([...items, item])
    })
    socket.service('items').on('patched', (item) => {
      setItems(items.map((t) => (t.id === item.id ? item : t)))
    })
    socket.service('items').on('removed', (item) => {
      setItems(items.filter((t) => t.id !== item.id))
    })
  })

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

This works, but mapping over the entire array for every patch or filter for every removal feels inefficient and verbose.

Using a Map:

ES6 introduced the Map object (see MDN Docs), which holds key-value pairs. Using the id as the key simplifies updates significantly.

// react component
function ListComponent() {
  const [items, setItems] = useState(new Map())

  useEffect(() => {
    socket
      .service('items')
      .find()
      .then(({ data }) => {
        // Create Map from array: [id, item]
        setItems(new Map(data.map((d) => [d.id, d])))
      })
  }, [])

  useEffect(() => {
    ;['created', 'patched', 'removed'].forEach((event) => {
      socket.service('items').on(event, (item) => {
        if (event === 'removed') {
          items.delete(item.id)
        } else {
          items.set(item.id, item)
        }
        // Trigger re-render with new Map reference
        setItems(new Map(items))
      })
    })
  })

  return (
    <ul>
      {Array.from(items).map(([id, item]) => (
        <li key={id}>{item.name}</li>
      ))}
    </ul>
  )
}

The key is creating a new Map reference (new Map(items)) to trigger the state update. This approach simplifies the logic—no more mapping or filtering the entire collection just to update one item.

If you have feedback, reach out on Twitter.