# State is a Record of the Past, Not a Claim About the Present

One of the most common React mistakes I see is using `useEffect` to keep two pieces of state in sync. It seems intuitive, but it introduces a subtle bug — and more importantly, it reveals a misunderstanding about what state actually represents.

## The Problem

Imagine you have a list of items and a selected item ID. When items change, you need to clear the selection if the selected item no longer exists.

Here's the instinctive (but wrong) approach:

```tsx
function ItemList() {
  const [selectedId, setSelectedId] = useState<string | null>(null)
  const items = useItems()

  useEffect(() => {
    if (selectedId !== null && !items.some((item) => item.id === selectedId)) {
      setSelectedId(null)
    }
  }, [items, selectedId])

  return <List items={items} selectedId={selectedId} />
}
```

This looks reasonable, but there's a hidden problem: **for one render,** `selectedId` points to an item that doesn't exist.

React renders with stale state, *then* the effect runs and fixes it. During that intermediate render, any code that assumes `selectedId` is valid will break.

But there's a deeper issue here — this code misunderstands what state is for.

## The Mental Shift

When you call `setSelectedId("abc")`, you're not declaring "item abc is selected." You're recording an event: **"the user selected item abc."**

State is a record of what happened. It's historical. It doesn't make claims about whether that selection is still valid — that's a separate concern.

This reframing changes everything. Watch what happens when we rename the variable to reflect what it actually holds:

```tsx
function ItemList() {
  const [lastSelectedId, setLastSelectedId] = useState<string | null>(null)
  const items = useItems()

  const selectedId = useMemo(
    () => items.some((item) => item.id === lastSelectedId) ? lastSelectedId : null,
    [items, lastSelectedId],
  )

  return <List items={items} selectedId={selectedId} />
}
```

Now the code reads honestly:

* `lastSelectedId` — the last ID the user selected (a fact about the past)
    
* `selectedId` — the currently valid selection (derived from present reality)
    

There's no `useEffect`. No sync. No intermediate broken render. The derivation happens synchronously during render, so `selectedId` is **always** consistent with `items`.

## The Principle

**State records what happened. Derived values describe what's true now.**

When you try to store "what's true now" directly in state, you end up fighting to keep it updated — hence the `useEffect` trap. But when you store the raw historical fact and derive current truth from it, everything stays in sync automatically.

## A Reusable Hook

This pattern comes up often enough that it's worth extracting:

```tsx
function useSelection<TItem, TKey extends keyof TItem>(
  items: TItem[],
  identifierKey: TKey,
): [TItem[TKey] | null, (id: TItem[TKey] | null) => void] {
  const [lastSelectedId, setLastSelectedId] = useState<TItem[TKey] | null>(null)

  const selectedId = useMemo(
    () => items.some((item) => item[identifierKey] === lastSelectedId) ? lastSelectedId : null,
    [items, identifierKey, lastSelectedId],
  )

  return [selectedId, setLastSelectedId]
}
```

Usage is clean:

```tsx
function ItemList() {
  const items = useItems()
  const [selectedId, setSelectedId] = useSelection(items, 'id')

  return <List items={items} selectedId={selectedId} />
}
```

The hook encapsulates the pattern: store the user's action, derive the valid state. Consumers get a `selectedId` that's always safe to use.

## Takeaway

Next time you reach for `useEffect` to "fix" state based on other state, pause. Ask yourself: *am I storing a fact about the past, or am I trying to store current truth?*

Store the past. Derive the present.
