
Stop Using useEffect for State Syncing: Common React Anti-Patterns
Muhammad Kamran
When I first really got the hang of React, useEffect became my hammer, and every problem looked like a nail. If I had two pieces of state that needed to stay in sync, I’d throw them into an effect.
It felt clever at the time—like I was creating a reactive machine. But eventually, I realized I wasn't solving problems; I was creating bugs. I was managing state I didn't even need.
React state is powerful, but the biggest mistake developers make (myself included) is storing values that don't need to be stored.
Derived Values Don't Belong in State
The classic mistake is redundancy. If you have firstName and lastName, you don't need a fullName state variable.
The "Syncing" Trap
const [firstName, setFirstName] = useState('Muhammad');
const [lastName, setLastName] = useState('Kamran');
const [fullName, setFullName] = useState('');
// This is an anti-pattern
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
Why this is bad:
Render Cycle Waste: The component renders once with the old name, the effect runs, sets the state, and triggers another render.
Complexity: You now have to track three variables instead of two.
The Fix:
Calculate it during the render. If firstName or lastName changes, the component re-renders anyway, so fullName it will always be fresh.
const fullName = `${firstName} ${lastName}`;
Filtering Data: Compute, Don't Copy
I often see developers duplicating data into a "filtered" state when building search features.
useEffect(() => {
// We are duplicating data into a new state variable unnecessarily
const result = items.filter(item => item.includes(query));
setFilteredItems(result);
}, [query, items]);
This creates a disconnect. You have items (source of truth) and filteredItems (a copy). If you forget to update the effect, your UI breaks.
The Cleaner Way
Just derive it.
const filteredItems = items.filter(item => item.includes(query));
A Note on Performance:
If items contains thousands of rows, filtering on every render can be slow. This is the one time you should wrap the calculation in useMemo, not useEffect.
const filteredItems = useMemo(() => {
return items.filter(item => item.includes(query));
}, [items, query]);
Form Validation is Just Logic
You don't need to wait for an effect to tell you if a form is valid.
The Delay
useEffect(() => {
setIsValid(text.length > 5);
}, [text]);
This introduces a micro-delay. The user types, the render happens, the effect runs, state updates, and another render happens to enable the button.
You can calculate it immediately.
const isValid = text.length > 5;
Now, the "Submit" button state updates in the exact same frame as the user's keystroke.
The key Prop Trick (Resetting State)
This is a pattern that even experienced developers miss. If you need to reset a component's state (like a form) when a user ID changes, don't use an effect.
The Flicker Prone Way
useEffect(() => {
setEmail('');
}, [userId]);
This often causes a split-second "flicker" where the old user's data is visible before the wipe happens.
The React Way
Use the key prop. When the key changes, React completely destroys the old component instance and mounts a fresh one. State is reset automatically.
<EditProfile key={userId} userId={userId} />
You may want to learn in detail. I have attached the link to the official React docs for your reference.
Enjoyed this article?
Check out more of my content or get in touch if you'd like to work together on your next project.