# Stop Using Arrays for Real-Time State in React

> Using a native Map instead of an array eliminates O(n) scans for updates and simplifies real-time event handlers in React.

- Author: Brian Kimball
- Published: 2023-11-06
- Tags: development, reactjs, javascript
- Canonical: https://brian-kimball.com/blog/react-state-map/
- Markdown: https://brian-kimball.com/blog/react-state-map/index.md

## The Problem with Arrays

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:**

```javascript
// 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 Instead

ES6 introduced the `Map` object (see [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)), which holds key-value pairs. Using the `id` as the key simplifies updates significantly.

**Using a Map:**

```javascript
// 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](https://twitter.com/_bskimball).
