Stop Using Arrays for Real-Time State in React
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:
// 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), which holds key-value pairs. Using the id as the key simplifies updates significantly.
Using a Map:
// 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.
Surveyor: A Field-Tested Network Discovery Tool for MSPs
From Raspberry Pi prototype to Wails desktop app: how Surveyor replaces manual nmap commands with stealth scanning and RFC 1918 auto-discovery.
TanStack Hono: A Lightweight SSR Monolith Without the Meta-Framework Weight
Single-process SSR + CSR with type-safe RPC, Vite, React, and Hono. The goal is low operational complexity without giving up dynamic UI.
A Modern Feathers.js Stack on IBM i: Real-Time APIs on DB2
Real-time WebSockets, REST APIs, and a Vite React frontend running directly on IBM i using Feathers.js and a custom Knex adapter for DB2.