DEV Community

Satyam Kumar
Satyam Kumar

Posted on

useState and useEffect

We’re diving into two of the most essential React hooks: useState and useEffect. Whether you’re a beginner or preparing for job interviews.

1. What are React Hooks?

React Hooks are special functions that let you use React features like state and lifecycle methods in functional components. Before hooks, we relied on class components, but hooks have simplified development and made code more readable.

2. useState

  • The useState hook is used to add state to functional components. It returns two things: the current state and a function to update that state.

Example

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode

in this example we are simply setting the count with the help of useState on click event. When we will click the button it will increase the count with 1.

Common Mistakes:

  • "Updating state directly: count++ — this won't work! Always use the setState function."

  • "Using useState outside a functional component. Hooks must be called within the component body."

  • Inside Another Function or Conditional Block (Not Allowed)
    React hooks must be called in the same order on every render. If you try to call useState conditionally or inside a nested function, React can't guarantee this, leading to errors.

Example: Incorrect Usage

function Counter() {
  if (true) { // This is a conditional block
    const [count, setCount] = useState(0); // ❌ This will throw an error
  }

  return <div>Invalid Usage</div>;
}
Enter fullscreen mode Exit fullscreen mode

Error Message:
"React Hook 'useState' is called conditionally. React Hooks must be called in the exact same order in every component render."

3. useEffect

  • useEffect is a React hook that handles side effects in functional components, like data fetching, subscriptions, or manually updating the DOM.

  • Syntax:

useEffect(() => {
  // Your side effect code here
  return () => {
    // Cleanup code here (optional)
  };
}, [dependencies]);
Enter fullscreen mode Exit fullscreen mode
  • The dependencies array controls when the effect runs:
    Empty ([]): Runs only once, when the component mounts.
    No array: Runs after every render.

  • Specified dependencies: Runs only when those values change.

Example: Timer with useEffect

import React, { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);

    return () => clearInterval(interval); // Cleanup the interval on unmount
  }, []); // Empty array ensures this runs only once

  return <h1>Seconds: {seconds}</h1>;
}

export default Timer;
Enter fullscreen mode Exit fullscreen mode
  • How it works:
    • The useEffect runs after the component mounts, setting up an interval to update seconds.
    • The cleanup function clears the interval when the component unmounts, preventing memory leaks.

Common Mistakes with useEffect

  1. Missing Dependencies: If you forget to include variables in the dependency array, the effect might not behave as expected.
useEffect(() => {
  console.log(data); // `data` should be in the dependency array
}, [data]);
Enter fullscreen mode Exit fullscreen mode
  1. Infinite Loops: Updating state inside an effect without proper dependencies can cause the effect to run infinitely:

Enter fullscreen mode Exit fullscreen mode

useEffect(() => {
setCount(count + 1); // This causes an infinite loop
});

Advanced Use Cases

  1. Updating Objects in useState When working with objects, always copy the previous state using the spread operator to avoid overwriting unintended properties.
const [user, setUser] = useState({ name: '', age: 0 });

function updateName() {
  setUser((prevUser) => ({ ...prevUser, name: 'John' }));
}
Enter fullscreen mode Exit fullscreen mode
  1. Conditional Effects Only run an effect if a condition is met.
useEffect(() => {
  if (someCondition) {
    console.log('Condition met!');
  }
}, [someCondition]); // Runs when `someCondition` changes 
Enter fullscreen mode Exit fullscreen mode
  1. Fetching Data A common use of useEffect is fetching data from an API.
useEffect(() => {
  async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    const result = await response.json();
    console.log(result);
  }
  fetchData();
}, []); // Runs only once
Enter fullscreen mode Exit fullscreen mode

Job Interview Questions

  1. What are hooks? Can they be used in class components?
    Hooks are functions that let you use React features in functional
    components. They cannot be used in class components.

  2. What happens if you don’t provide a dependency array in useEffect?
    The effect runs after every render, potentially leading to
    performance issues or infinite loops.

  3. How do you clean up effects?
    By returning a cleanup function from useEffect:

useEffect(() => {
  const interval = setInterval(() => console.log('Running'), 1000);
  return () => clearInterval(interval); // Cleanup
}, []);
Enter fullscreen mode Exit fullscreen mode
  1. Why do we use functional updates in useState? To ensure the state is updated based on the most recent value when the updates depend on the previous state:
setState((prevState) => prevState + 1);
Enter fullscreen mode Exit fullscreen mode

There is a question related to this concept asked commonly in interviews.

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount(count + 1); // Updates count by +1
    setCount(count + 1); // Still uses the same count value (not updated yet)
    setCount(count + 1); // Again, uses the same count value
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode

Let's say if we run this code what you expect to happen. The thing is here you will think if my current count is 1 then it will be 4 after clicking the button but in reality it gonna update the count by just 1 that is count will become 2.

Let's understand this, everytime we do setState(count+1). It is using the same count not the currently updated count because the count will update only at the time of rerendering. When you update a state in React like setState(count + 1) multiple times in a row, the updates do not immediately reflect in the current render. Instead, React batches state updates for performance, and each setState call uses the current value of the state at the time of its execution.

solution:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount((prevCount) => prevCount + 1); // Uses the latest value of count
    setCount((prevCount) => prevCount + 1); // Updates based on the new value
    setCount((prevCount) => prevCount + 1); // Updates again based on the latest value
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode

When using the functional form of setState, React ensures that each update is based on the most recent state, even if multiple updates are queued.

  1. What Happens Without Clearing the Interval?
  2. Interval Keeps Running:
    When you set an interval using setInterval, the JavaScript runtime keeps executing the callback function at the specified time interval, regardless of whether the component is still in the DOM.

  3. Access to Unmounted Component:
    The interval callback may try to update the state or interact with the DOM of the component that has already been unmounted. Since the component no longer exists, this leads to warnings or errors.

Example warning in React:
Can't perform a React state update on an unmounted component.

  1. Memory Leak: Since the interval is still running and holding a reference to the callback function, memory associated with the component (e.g., variables, state) cannot be garbage collected. This increases memory usage over time and can degrade application performance.

Top comments (0)