DEV Community

Cover image for Common Mistakes Developers Make with useState in React (And How to Fix Them)
Ujjwal Kar
Ujjwal Kar

Posted on

Common Mistakes Developers Make with useState in React (And How to Fix Them)

React's useState hook is an essential tool for managing state in functional components, but it's easy to stumble into some common pitfalls. Whether you’re just starting out with React or have been working with it for a while, avoiding these mistakes can save you from unexpected bugs and performance issues.

Let’s walk through 10 frequent mistakes and how you can avoid them to write cleaner and more efficient code.

1. Wrong Initial State Type

One of the most common issues arises when the initial state type doesn’t match the type expected during state updates.

❌ Mistake: Initial State Type Mismatch

const [count, setCount] = useState(0);
setCount("1"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Use TypeScript or specify the type explicitly.

const [count, setCount] = useState<number>(0);
setCount(1); // No issues now.
Enter fullscreen mode Exit fullscreen mode

2. Not Using Functional Updates

When updating state based on the previous value, referencing the current state directly can lead to stale values, especially in async operations.

❌ Mistake: Using Current State Directly

setCount(count + 1); // Can cause bugs in async scenarios.
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Use the functional form for safe updates.

setCount((prevCount) => prevCount + 1); // Ensures you always have the latest value.
Enter fullscreen mode Exit fullscreen mode

3. Storing Derived State

Avoid storing values in state that can be derived from other state or props. This can lead to unnecessary re-renders and synchronization issues.

❌ Mistake: Storing Derived State

const [count, setCount] = useState(0);
const [doubleCount, setDoubleCount] = useState(count * 2);
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Derive the value during render instead of using state.

const [count, setCount] = useState(0);
const doubleCount = count * 2; // No need to store this in state.
Enter fullscreen mode Exit fullscreen mode

4. State Updates Inside the Render Phase

Calling setState inside the render phase is a recipe for infinite loops and performance issues.

❌ Mistake: Setting State During Render

const [count, setCount] = useState(0);
setCount(1); // Infinite loop!
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Trigger state changes in event handlers or effects.

const handleClick = () => setCount(1);
Enter fullscreen mode Exit fullscreen mode

5. Directly Mutating State

React won’t detect changes if you mutate the state directly, especially with arrays or objects.

❌ Mistake: Mutating State Directly

const [items, setItems] = useState<number[]>([1, 2, 3]);
items.push(4); // Mutation happens here, React won’t re-render!
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Return a new array or object to trigger re-renders.

setItems((prevItems) => [...prevItems, 4]); // Spread to create a new array.
Enter fullscreen mode Exit fullscreen mode

6. Undefined or Incorrect Types for Complex State

When dealing with complex state, not defining proper types can cause runtime issues and confusion.

❌ Mistake: Implicit Types Can Lead to Errors

const [user, setUser] = useState({ name: "", age: 0 });
setUser({ name: "John", age: "thirty" }); // Type error: Age should be a number.
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Define the shape of the state with correct types.

type User = { name: string; age: number };
const [user, setUser] = useState<User>({ name: "", age: 0 });
Enter fullscreen mode Exit fullscreen mode

7. Using State for Mutable Values (Like Timers)

Using useState for values that don’t affect rendering, such as timers, leads to unnecessary re-renders.

❌ Mistake: Using State for Mutable Values

const [timerId, setTimerId] = useState<number | null>(null);
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Use useRef for mutable values that don’t need re-rendering.

const timerIdRef = useRef<number | null>(null);
Enter fullscreen mode Exit fullscreen mode

8. Not Merging State Objects Properly

Unlike class components, useState does not merge updates automatically. Forgetting this can result in overwriting parts of your state.

❌ Mistake: Overwriting State Instead of Merging

const [user, setUser] = useState({ name: '', age: 0 });
setUser({ age: 25 }); // The 'name' field is now lost!
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Use the spread operator to merge state updates.

setUser((prevUser) => ({ ...prevUser, age: 25 })); // Merges with existing state.
Enter fullscreen mode Exit fullscreen mode

9. Using State for High-Frequency Updates

Tracking high-frequency values like window dimensions in state can cause performance issues due to excessive re-renders.

❌ Mistake: Using State for Frequent Updates

const [size, setSize] = useState(window.innerWidth);
window.addEventListener("resize", () => setSize(window.innerWidth));
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Use useRef or debounce to reduce the performance hit.

const sizeRef = useRef(window.innerWidth);
useEffect(() => {
  const handleResize = () => {
    sizeRef.current = window.innerWidth;
  };
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);
Enter fullscreen mode Exit fullscreen mode

10. Assuming State Updates Are Synchronous

React state updates are asynchronous, but many developers mistakenly assume that changes are applied immediately.

❌ Mistake: Assuming State Changes Are Immediate

setCount(count + 1);
console.log(count); // Logs the old value, not the updated one!
Enter fullscreen mode Exit fullscreen mode

✅ Solution: Use useEffect to track state changes and ensure the latest value is used.

useEffect(() => {
  console.log(count); // Logs the updated value after re-render.
}, [count]);
Enter fullscreen mode Exit fullscreen mode

Final Thoughts 💡

Avoiding these useState pitfalls will make your React code more robust, readable, and performant. Understanding how React’s state mechanism works and knowing the best practices will save you time debugging and enhance your overall development experience.

Do you have any useState tips or mistakes to share? Drop them in the comments below! 👇

Top comments (0)