BACK TO PORTFOLIO
HOME/FRONTEND ARCHITECTURE/REACT STATE

Your React UI May Be Buggy Because It Remembers Too Much

useState feels harmless. But every stored value becomes another value your component must update, synchronize, and keep correct.

ZA
Zain AliFull-Stack Developer
August 20269 min read
A React state diagram contrasting duplicated state with one clear source of truth

The Core Principle

React state is not just storage. It is a synchronization contract. Store the minimum information required to describe the interface, then derive everything else when possible.

I started thinking about this differently while building interfaces where the same underlying information appeared in several forms.

A list of links existed. Then there was a filtered version of that list. Then the number of filtered results. Then the currently selected item. Then flags derived from those values.

Individually, every state variable looked reasonable. Together, they created several representations of the same truth. And that is where seemingly random UI bugs begin.


The Pattern Looked Harmless

Imagine a simplified links interface:

Duplicated state
const [links, setLinks] = useState<Link[]>([]);
const [searchQuery, setSearchQuery] = useState("");
const [selectedCategory, setSelectedCategory] =
  useState<string | null>(null);

const [filteredLinks, setFilteredLinks] = useState<Link[]>([]);
const [filteredCount, setFilteredCount] = useState(0);
const [selectedLink, setSelectedLink] = useState<Link | null>(null);
const [isEmpty, setIsEmpty] = useState(false);

Nothing here immediately screams bad architecture. Every variable represents something visible in the UI. That is exactly the trap.

filteredLinks is not actually new information. Neither is filteredCount. Neither is isEmpty. They are consequences of information the component already has.

Duplicated State Creates Multiple Sources of Truth

Before

links + query + category

-> filteredLinks

-> filteredCount

-> isEmpty

-> selectedLink copy

After

links

searchQuery

selectedCategory

selectedLinkId

-> derive the current UI

The browser does not know which value represents the real truth. All of them are state, so React faithfully renders whichever values we gave it, even when those values disagree.

Store the Minimum. Derive the Rest.

Instead of asking where to store a value, I try to ask an earlier question: does the UI need to remember this at all?

Derived values
const [searchQuery, setSearchQuery] = useState("");
const [selectedCategory, setSelectedCategory] =
  useState<string | null>(null);
const [selectedLinkId, setSelectedLinkId] =
  useState<string | null>(null);

const filteredLinks = links.filter((link) => {
  const matchesQuery = link.title
    .toLowerCase()
    .includes(searchQuery.toLowerCase());

  const matchesCategory =
    !selectedCategory || link.categoryId === selectedCategory;

  return matchesQuery && matchesCategory;
});

const filteredCount = filteredLinks.length;
const selectedLink =
  links.find((link) => link.id === selectedLinkId) ?? null;
const isEmpty = filteredLinks.length === 0;

There is no synchronization effect. There is nothing to keep aligned. Whenever the source values change, React renders again and calculates the current answer from the current inputs.

Five Homes for UI Information

Local state

Independent user decisions: dialog open, search query, selected category.

Derived values

Counts, filtered collections, labels, empty states, and selected objects calculated from existing inputs.

Refs

Persistent values that should not update the interface when they change.

URL state

Filters, sorting, pagination, and navigation state that should survive refreshes or sharing.

Server-owned data

Remote data already owned by a cache or data-fetching layer unless the UI needs an intentional draft.

useEffect Is Often Where the Smell Becomes Visible

useEffect is not the problem. Effects are essential when React needs to synchronize with something outside React: DOM APIs, browser APIs, subscriptions, timers, network connections, or third-party libraries.

Is this effect connecting React to something external, or is it keeping my own state variables from disagreeing?

That question often reveals derived state hiding inside synchronization code.

The Decision Tree I Use Before Adding useState

Can it be calculated from existing inputs?

Derive it during render.

Does changing it need to update the UI?

State may be the right home.

Does it need to persist without rendering?

Use a ref instead of state.

Should refresh, sharing, or back/forward preserve it?

Consider URL state.

Does it come from remote data?

Let the data layer own it unless you need a local draft.

How I Audit an Overloaded React Component

When a component starts becoming difficult to reason about, I do not immediately split it into smaller files. First I inspect its state model.

  1. List every useState and write down what the component believes it must remember.
  2. Mark values that can be calculated: counts, filtered lists, booleans, formatted strings, selected objects, and status labels.
  3. Trace effects whose main job is A changed, so update B.
  4. Find duplicated ownership across props, fetched data, context, stores, the URL, and other state variables.
  5. Keep the smallest independent representation, then re-test create, delete, filter, search, select, clear filter, navigate, and refetch flows.

Derived State Is Not Always Wrong

A form might initialize from server data and then become an independent draft. An editor may preserve a historical snapshot. An optimistic interface might temporarily maintain a local version while a mutation is pending.

The distinction is ownership. If the UI intentionally creates a new version of information with its own lifecycle, state can make sense. What I want to avoid is accidental duplication because another copy felt convenient.

The Takeaway

Store less. Make each source of truth obvious.

Good React state architecture is not about avoiding useState. It is about making sure every stored value deserves to exist independently.

FREQUENTLY ASKED QUESTIONS

Frequently Asked Questions

Is having many useState hooks automatically bad?

No. Several independent user decisions can legitimately live in state. The trouble starts when state variables are just different versions of information the component already has.

Should filtered data be stored in React state?

Usually not. Store the original collection and the filter inputs, then calculate the filtered result from those values.

Should I use useMemo whenever I derive a value?

No. Derivation is about ownership and correctness. Memoization is a performance optimization, useful after measurement or when reference stability matters.

When should I use useEffect?

Use effects to synchronize React with something outside React: browser APIs, subscriptions, timers, network connections, or third-party libraries. Question effects that only keep React values aligned with each other.

Should selected objects be stored in state?

Sometimes, but storing a stable ID and deriving the object from the canonical collection often avoids stale object copies.