keepcalculator

06 Numbers and Powers

Stop Storing Filtered Lists in React State

Card No. 06·55599 · Updated · Standard formula, shown below

Yes, you should stop storing filtered lists in React state. Instead, derive the filtered list during rendering using the original data and the current filter criteria, because this keeps your state minimal, avoids synchronization bugs, and makes your components easier to reason about.

Why Deriving Is Better Than Storing

React’s core model is that state changes trigger re-renders. When you store a filtered list in state, you create two sources of truth: the original data and the filtered result. Every time the original data or the filter changes, you must remember to update the stored filtered list. If you forget, the UI shows stale data. Deriving the filtered list on each render means there is only one source of truth—the original data—and the filter is just a function of that data. This eliminates a whole class of bugs where state gets out of sync.

How Deriving Works in Practice

Instead of this:

const [filtered, setFiltered] = useState([]);
useEffect(() => {
  setFiltered(items.filter(item => item.category === category));
}, [items, category]);

You do this:

const filtered = items.filter(item => item.category === category);

The filtered list is recalculated every render, which is fine for lists of a few hundred or even a few thousand items. For larger datasets, you can use useMemo to memoize the computation and avoid re-filtering when dependencies haven’t changed:

const filtered = useMemo(
  () => items.filter(item => item.category === category),
  [items, category]
);

This gives you the same derived value but skips the filter when items or category haven’t changed.

When Storing Might Be Acceptable

There are a few rare cases where storing a filtered list in state might be reasonable:

  • You are working with a massive dataset (tens of thousands of items) and filtering is expensive—but even then, useMemo is usually the better answer.
  • You need to allow the user to manually reorder or edit the filtered results independently of the original data—but this usually means the filtered list is actually a separate piece of data, not just a view.
  • You are using an external state management library that benefits from having the filtered list in a store for other components to read—but again, you can derive it with a selector.

In most real applications, none of these apply. Start by deriving, and only introduce state if you have a concrete performance problem measured with profiling.

The Problem with useEffect for Filtering

A common anti-pattern is using useEffect to update the filtered list whenever the input changes. This causes an extra render: first with the old filtered list, then with the new one after the effect runs. It also makes the component slower and more complex. Worse, if the effect depends on multiple values, it’s easy to miss one, leading to stale results. Deriving avoids all of this because the filtered list is computed synchronously during render, so there’s no extra render and no chance of missing a dependency.

How to Choose the Right Approach

Ask yourself: is the filtered list a derived value or a piece of independent data? If it can be computed from other state and props, it’s derived. If it can be changed independently by the user (e.g., reordering, editing) and those changes should persist, then it’s independent state. For derived values, compute them during render. For independent data, store it in state. This simple rule covers 95% of cases.

Performance Considerations and Memoization

For small to medium lists, filtering on every render is negligible. For larger lists, useMemo is the right tool. But be careful: useMemo only helps if the dependencies actually change infrequently. If items changes often, memoization won’t help. Also, don’t over-optimize. React is fast, and premature memoization can make code harder to read. Measure first, then optimize.

Common Mistakes and Misconceptions

One misconception is that storing a filtered list in state is necessary for performance. In reality, it often makes performance worse because of the extra render from the effect. Another mistake is using useEffect to sync state when you could just derive it. This is sometimes called the “derived state anti-pattern” in React documentation. The official guidance is to avoid redundant state—state that can be computed from other state or props.

Frequently Asked Questions

Is it okay to use useMemo for filtering?

Yes, useMemo is a great way to optimize derived data. It caches the result and only recomputes when dependencies change. Use it when filtering is expensive or the component re-renders often. But don’t use it for every derived value; it adds a small overhead and can make code less readable if overused.

What if the filter logic is complex?

That’s fine. You can extract the filtering logic into a pure function and call it in your component. This keeps the component clean and makes the logic testable. For example: const filtered = filterItems(items, filters);.

Does this apply to sorting and pagination too?

Yes, absolutely. Sorting and pagination are also derived values. Compute them during render based on the original data and the current sort/pagination settings. This avoids the same synchronization issues.

I’m using Redux; should I store filtered lists there?

In Redux, the same principle applies. Store the original data and the filter criteria in the store, and derive the filtered list with a selector (e.g., using createSelector from Reselect). This keeps the store minimal and selectors can be memoized for performance.

What about server-side filtering?

If you’re fetching filtered data from a server, then the filtered list is not derived from local state—it’s fetched data. In that case, storing it in state is correct. But if you’re filtering data you already have, derive it locally.

Can I use a library like lodash for filtering?

Yes, you can use any utility library for the filtering logic itself. The point is not to store the result in state. For example: const filtered = _.filter(items, item => item.category === category);.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →