React Effect Hook React Typescript

Mar 11th, 2022 - written by Kimserey with .

When using React hooks, we often come across scenarios where we need to perform side effects. For that, a built-in hook is in our disposal; useEffect. In today’s post, we’ll look at how to use useEffect hook and in which context it should be used.

useEffect Hook

useEffect hook is used to decouple functionality from rendering of the component.

Let’s take the example of a component in an app:

1
2
3
4
5
6
7
8
9
10
11
12
const Example: React.FC<{ count: number }> = ({ count }) => {
  const [localCount, setLocalCount] = useState(0);

  console.log("", { count, localCount });

  return (
    <div>
      <p>{localCount}</p>
      <button onClick={() => setLocalCount(localCount + 1)}>Local count</button>
    </div>
  );
};

We have an Example component which accepts a count and a state counter setting localCount.

The side effect console.log will print on each rendering of Example, if we use it as followed in our main App:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const App: React.FC = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <div>
        <p>{count}</p>
        <button onClick={() => setCount(count + 1)}>Count</button>
      </div>
      <div>
        <Example count={count}></Example>
      </div>
    </>
  );
};

then the console.log will print on each render of App or each render of Example which would occur when the state changes.

To turn our side effect into useEffect, we simply wrap:

1
2
3
useEffect(() => {
  console.log("", { count, localCount });
});

Our component then becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Example: React.FC<{ count: number }> = ({ count }) => {
  const [localCount, setLocalCount] = useState(0);

  useEffect(() => {
    console.log("", { count, localCount });
  }]);

  return (
    <div>
      <p>{localCount}</p>
      <button onClick={() => setLocalCount(localCount + 1)}>Local count</button>
    </div>
  );
};

The function given to useEffect is called at each update, this is how count and localCount can have the correct value.

When we click on the buttons, we can see in the logs that the console.log get printed with the right value on each click:

1
2
3
4
5
6
{count: 0, localCount: 0}
{count: 1, localCount: 0}
{count: 2, localCount: 0}
{count: 3, localCount: 0}
{count: 3, localCount: 1}
{count: 3, localCount: 2}

With effect, we can also provide a function as return so that we can execute teardown functionalities:

1
2
3
4
5
6
7
useEffect(() => {
  console.log("", { count, localCount });

  return () => {
    console.log("teardown", { count, localCount });
  };
});

then before the effect is executed, the previous instance will be teardowned.

Skipping Updates

It may be too excessive to execute the effect on each render as when the parent gets rendered, it also trigger a render on the component, or when props update or state updates, it will trigger a render.

If we want to control the execution of useEffect we can provide a second argument array to state the dependencies of the effect and when it should be executed.

For example our effect depends on count and localCount so we can provide the following:

1
2
3
4
5
6
7
useEffect(() => {
  console.log("", { count, localCount });

  return () => {
    console.log("teardown", { count, localCount });
  };
}, [count, localCount]);

With this, the effect will run only when count or localCount are updated. We can verify the behavior as if we only use [localCount], on changes of count, the effect will not be called.

On first render, the effect is ran once, so if we want to run only once, we can provide an empty array []:

1
2
3
4
5
6
7
useEffect(() => {
  console.log("", { count, localCount });

  return () => {
    console.log("teardown", { count, localCount });
  };
}, []);

With this setup, the effect will run only once and never again. And if we don’t provide the second argument, the behavior is as described in the first section which is that the effect is ran on each update.

Lastly a special variant of useState and useEffect, if our effect depends on a state that the effect itself modifies, we would be entering an infinite loop where the state update triggers the effect; and the effect changes the state.

For example the following interval set in useEffect:

1
2
3
4
5
useEffect(() => {
  const interval = setInterval(() => setLocalCount((count + 1), 1000);

  return () => clearInterval(interval);
}, []);

This will always return 1 because count will always remain 0 as the effect is ran only once with count = 0. So every time the interval triggers, count will be 0.

But the problem is that if we add [count] as dependency of the effect, then it will enter a loop where the interval will be teardown and recreated every time the interval kicks in.

The solution for that is the functional update form of setLocalCount:

1
2
3
4
5
useEffect(() => {
  const interval = setInterval(() => setLocalCount((c) => c + 1), 1000);

  return () => clearInterval(interval);
}, []);

That way, the state update is based on a previous state provided inside the function and we don’t reference external values.

And that concludes today’s post!

Conclusion

Today we looked at useEffect hook and the different way of using it. We started by looking at when we should be using the hook and we then moved on to how to remove excessive without falling into any pitfall. I hope you liked this post and I’ll see you on the next one!

External Sources

Designed, built and maintained by Kimserey Lam.