The Problem: useEffect as State Reset Trigger
A React developer recently encountered a peculiar pattern in a new codebase: a useEffect that resets a piece of state (selectedMovieId) whenever any of three filter states (selectedYear, selectedCategory, selectedUserRating) change. The code looked like this:
const [selectedMovieId, setSelectedMovieId] = useState("");
useEffect(() => {
setSelectedMovieId(null);
}, [selectedYear, selectedCategory, selectedUserRating]);
The intent is clear: when the user changes a filter (year, category, rating), the currently selected movie should be deselected because the table data will reload. However, the developer who wrote this chose to express that logic declaratively via a useEffect dependency array rather than imperatively inside the event handlers that change the filters.
The author of the DEV.to post, arikaturika, argues this is a code smell. They prefer to reset the selection directly in each filter's change handler:
const handleYearChange = (year: YearType) => {
setSelectedYear(year);
setSelectedMovieId(null);
};
const handleCategoryChange = (category: CategoryType) => {
setSelectedCategory(category);
setSelectedMovieId(null);
};
const handleRatingChange = (rating: RatingType) => {
setSelectedUserRating(rating);
setSelectedMovieId(null);
};
The Core Debate: Declarative vs. Imperative Reset
This isn't just a style preference. The two approaches have different trade-offs:
The useEffect Approach (Declarative)
- Pros: Centralizes the reset logic; if you later add a new filter, you only need to add it to the dependency array. The effect automatically handles any future filter changes without modifying event handlers.
- Cons: The dependency array must be kept in sync. If a developer adds a new filter but forgets to update the
useEffectdependencies, the selection won't reset. Also,useEffectruns after render, meaning there's a brief moment where the old selection is still visible. This can cause visual flicker or inconsistent state during the same render cycle.
The Event Handler Approach (Imperative)
- Pros: Explicit and easy to understand. The reset happens synchronously during the same event handler, so there's no extra render cycle. Adding a new filter requires adding
setSelectedMovieId(null)in its handler, which is harder to miss than updating a dependency array. - Cons: Duplication — every filter handler must include the reset call. If you have many filters, this becomes repetitive and error-prone (someone might forget to add it).
When Does Each Pattern Make Sense?
The article's author suggests that the imperative approach is generally better because it's more straightforward and less prone to future bugs. However, there are scenarios where the useEffect pattern might be acceptable:
- Multiple unrelated state changes trigger the same side effect. For example, if you need to clear a selection when any of 10 different filters change, and those filters are updated in various places (e.g., URL params, external stores), a
useEffectcan be a clean way to centralize the logic. - The reset logic is complex and depends on the new values of the dependencies. In that case, you need to read the new values inside the effect anyway.
- You are using a state management library like Redux or Zustand where the filter changes might be dispatched from multiple components. In that case, a
useEffectin a single component can react to those changes without coupling to every dispatch site.
The Real Code Smell: Overusing useEffect for Derived State
A deeper issue is whether selectedMovieId should even be a state variable that needs resetting. In the original example, the selection is meaningless when the filters change because the underlying data changes. An alternative is to compute the selection as derived state from the filter values. For instance, store the selection as a key-value pair where the key includes the filter values:
const selectionKey = `${selectedYear}-${selectedCategory}-${selectedUserRating}`;
const [selectionMap, setSelectionMap] = useState>({});
const selectedMovieId = selectionMap[selectionKey] || null;
Now, when filters change, selectionKey changes, and selectedMovieId automatically becomes null without any effect or handler modification. This pattern eliminates the need for manual reset entirely.
What the React Docs Say
The official React documentation warns against using useEffect for synchronizing state that can be computed from other state. In the new React 18 docs, they recommend "you might not need an effect" for many cases. Resetting state based on prop changes is a classic example where an effect is often unnecessary.
Conclusion: Prefer Handlers, but Know the Exceptions
For the specific case of resetting a selection when filters change, the handler approach is cleaner and more maintainable. It avoids the pitfalls of dependency arrays and extra render cycles. However, if you have many filters updated from disparate sources, a useEffect can be a pragmatic solution — just be diligent about keeping dependencies up to date.
As a rule of thumb: if you can express the state update directly in the event handler, do it. If the trigger is a combination of many state changes that you don't control in one place, consider useEffect or, better yet, derive the state from the dependencies.
Now, review your own codebase. Search for useEffect calls that exist only to set state based on other state. Could those be moved to event handlers or derived state? Your future self (and your teammates) will thank you.

