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
HookuseEffect
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.
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!
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!