Frontend
Back to frontend track
Deprecated

Solution

React Hooks: useEffect - 1

The useEffect hook is notorious to cause majority of bugs in most React projects. Can you solve a similar bug?

Walkthrough

Compare with the solution.

Keep the explanation and implementation side by side.

Hooks: useEffect-1 | React Coding | Easy Practice Question | Frontend Hire

Watch alongside the notes.

Open video

The bug is evident whenever we reset the timer. But it also can be noticed when the timer is left to run for a few seconds.

The seconds state variable is set inside a setInterval which itself is inside a useEffect.

The current useEffect runs every time the seconds state variable changes and the setInterval is not being cleared resulting in a memory leak.

We can fix this bug with a simple useEffect cleanup function.

App.jsx
React.useEffect(() => {
  const interval = setInterval(() => {
    setSeconds(seconds + 1);
  }, 1000);
 
  return () => {
    clearInterval(interval);
  };
}, [seconds]);

Though the bug is fixed, we prefer a different way to set the state which allows us to not pass the seconds value to the useEffect dependency array. setSeconds can take an updater function

App.jsx
React.useEffect(() => {
  const interval = setInterval(() => {
    setSeconds(seconds + 1);
    setSeconds(seconds => seconds + 1);
  }, 1000);
 
  return () => {
    clearInterval(interval);
  };
}, [seconds]);
}, []);

Bonus

Creating a useTimer hook is a great way to abstract away the timer logic.

useTimer.jsx
import React from 'react';
 
export default function useTimer() {
  const [seconds, setSeconds] = React.useState(0);
 
  React.useEffect(() => {
    const interval = setInterval(() => {
      setSeconds((seconds) => seconds + 1);
    }, 1000);
 
    return () => {
      clearInterval(interval);
    };
  }, []);
 
  const resetTimer = () => {
    setSeconds(0);
  };
 
  return { seconds, resetTimer };
}

All we had to do was extract the useState and useEffect logic into the useTimer hook. The useTimer hook returns an object containing the seconds state and a resetTimer function which we use back in the App component.

App.jsx
import useTimer from './useTimer';
 
export default function App() {
  const [seconds, setSeconds] = React.useState(0);
 
  React.useEffect(() => {
    const interval = setInterval(() => {
      setSeconds((seconds) => seconds + 1);
    }, 1000);
 
    return () => {
      clearInterval(interval);
    };
  }, []);
  const { seconds, resetTimer } = useTimer();
 
  return (
    <div className="p-4 text-center">
      <p>Timer: {seconds} Seconds</p>
      <button
        className="rounded bg-red-500 p-2 hover:bg-red-600"
        onClick={() => setSeconds(0)}
        onClick={resetTimer}
      >
        Reset Timer
      </button>
    </div>
  );
}