With great features, comes great understanding — useEffect, the right way 🌀
After React 16.8, life was never the same as hooks were introduced. This was around the time I started coding so I didn’t get to experience the pain of class components so much.
I like to say “with great features, comes great understanding”. If you are using a useEffect in the wrong way, you might introduce more bugs and have a negative impact on performance. Removing unnecessary useEffects will make your code easier to read, faster to execute and with fewer bugs.
These are the topics that are we going to cover, but feel free to skip the basics:
· 📗 Intro
· 📝 Syntax
· 👊 Behavior
· ✨ Better Usages
· 👀 Maybe we don’t even need a useEffect
📗 Intro
The useEffect
hook that allows us to perform side effects in functional components. It serves purposes similar to lifecycle methods in class components (componentDidMount
, componentDidUpdate
, and componentWillUnmount
).
In other words, the hook will execute when the component is rendered, changed or unmounted.
📝 Syntax
useEffect(callback,[dependencies]);
In the code snippet above, the callback
refers to the function executed when the useEffect
hook is triggered. This function can be written as an arrow function. The dependencies
parameter is an optional array of variables; when any of these variables change, the useEffect
hook re-runs the callback
.
👊 Behavior
There are several ways the useEffect
will act:
1. We can trigger the useEffect
each time the screen is rendered.
If you don’t provide an arguments as the dependency array, the useEffect will execute every time your component updates.
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
useEffect(() => {
console.log("I have executed");
});
In this example, useState
initializes the state variables firstName
and lastName
, along with their respective setter functions, setFirstName
and setLastName
. The useEffect
hook, when used without a dependency array, runs on every render of the component. As a result, "I have executed" will print not only when firstName
or lastName
changes but also on every re-render of the component, since each render is considered a component update.
2. Specific variable change
You can make the useEffect
run when specific state changes.
The useEffect will execute if either of the values in the list has changed. In this case, either firstName
or lastName
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
useEffect(() => {
console.log("I have executed");
}, [firstName, lastName]);
3. Initial re-render
As we saw, passing a dependency array will execute each time there is a change in the dependency value, an actual comparison occurs.
Hence, if we pass an empty dependency array, there would never be a change again, and so the useEffect
will only execute once, on the initial render.
useEffect(() => {
console.log('I will execute only once');
}, []);
4. Cleanup
Every time the component is destroyed we will run the code in the return block
useEffect(() => () => {
// class components: componentWillUnmount
console.log('I will run when the component is destroyed');
});
✨ Better Usages
It’s always important to keep in mind clean code. When using useEffects everywhere in the project, it might be hard to pinpoint the exact piece of code that we are looking for. Some great advice I have learnt at work is to encapsulate the hook with the logic.
The benefits are clear: we avoid repetition and have the logic in a dedicated hook that we can later modify as needed and even reuse else where.
For instance, when you first see this component , there is a lot of logic:
const ButtonProps = {
handleOnClick: VoidFunction,
};
const Button = ({ handleOnClick }: ButtonProps) => {
const [isFirstTimePress, setIsFirstTimePress] = useState(true);
const [title, setTitle] = useState('');
useEffect(() => {
const title = isFirstTimePress ? 'hello' : 'welcome back';
setIsFirstTimePress(false);
setTitle(title);
}, []); // <-- great ex of where we only need the hook to execute only once
return <button onClick={handleOnClick}>{title}</button>;
};
We can make it better by separating the UI with the logic of how to render title. Like so:
// Button.tsx
const ButtonProps = {
handleOnClick: VoidFunction,
};
const Button = ({ handleOnClick }: ButtonProps) => {
const { title } = useFindTitle().states;
return <button onClick={handleOnClick}>{title}</button>;
};
// useFindTitle.ts
const useFindTitle = () => {
const [isFirstTimePress, setIsFirstTimePress] = useState(true);
const [title, setTitle] = useState('');
useEffect(() => {
const title = isFirstTimePress ? 'hello' : 'welcome back';
setIsFirstTimePress(false);
setTitle(title);
}, []); // <-- great ex of where we only need the hook to execute only once
return {
states: {
title,
},
};
};
Look how clean and logic-independent Button.tsx
has become. useFindTitle
can also be used in other components as needed.
The custom hooks can return states
, actions
or selectors
which would make it even more reusable.
👀 Maybe we don’t even need a useEffect
Don’t use a useEffect to handle user events (ex: click)
For example, we make a game where the user number guess the number. We want to print a snack bar to let the user know if they are close or far from the correct number (1 in our case).
// Game.tsx
const [number, setNumber] = useState(1);
const renderSnackBar = (msg: string) => {
// will render a snackbar (notification)
}
useEffect(() => {
if(number === 1){
renderSnackBar("You guessed it!!!!");
}
if(number > 5){
renderSnackBar("Colder");
}
if(number < 0 ){
renderSnackBar("Buddy, its a positive number");
}
if(number < 5 && number > 2){
renderSnackBar("Hotter");
}
,[number]});
<label> Add your number here: </label>
<input onChange={(e) => setNumber(e.target.value)}/>
We have a useEffect
that has a dependency array of number
which will execute the callback each time number changes.Instead of this complexity, we can handle the logic directly in the onClick
callback. This approach is often more efficient because you know exactly when the action will be executed—immediately when the user clicks the button or triggers the event.
// Game.tsx
const [number, setNumber] = useState(1);
const renderSnackBar = (msg: string) => {
// will render a snackbar (notification)
}
const handleOnChange = (e: Event) => {
cosnt number = e.target.value;
setNumber(number); // update the state
if(number === 1){
renderSnackBar("You guessed it!!!!");
}
if(number > 5){
renderSnackBar("Colder");
}
if(number < 0 ){
renderSnackBar("Buddy, its a positive number");
}
if(number < 5 && number > 2){
renderSnackBar("Hotter");
}
}
<label> Add your number here: </label>
<input onChange={handleOnChange}/>
Thats it!
& be careful, when using useEffects
, it has some side effects ;)
Lara,
Until next time 🍃
P.S
For more detailed examples and explanations, visit the React documentation.