Solution For disabling UseEffect initial render
React’s useEffect hook has indeed made life a lot easier. But there are times when you would most certainly miss the fine-grained control class-based components give you over a component’s lifecycle. One such time is when you want to perform a side effect when a component’s state changes.
Of course, you can pass the state variable as a dependency into the useEffect hook but the problem is the callback function gets called during the initial render as well.
Take this little example. Let’s say I have an app that has a button. Once the button is clicked, I need to display a text that says “The button has been pressed!”. A simple way of doing this is to have a Boolean state value that will be set to true
once the button is clicked. The text can be displayed only if the Boolean value is true
.
useEffect hook’s callback gets called during the initial render
However, for the sake of using the useEffect hook, let’s have two state values. Let me name the first state “press” and the second state “pressed”. Now, once the button is clicked, the press
state is set to true. Then let's call the useEffect hook and pass a callback function that would set pressed
to true when press changes. The text should be shown only if pressed
is set to true.
export default function App() {
const [press, setPress] = useState(false);
const [pressed, setPressed] = useState(false);
useEffect(() => {
setPressed(true);
}, [press]);
return (
<div className="App">
{pressed && <div>The button has been pressed!</div>}
<button
onClick={() => {
setPress(true);
}}
>
Click!
</button>
</div>
);
But on running the app, you will realize that the text is shown even before the button is clicked. The reason? Well, useEffect’s callback function gets called not only when one of the dependencies changes but also during the initial render.
To prevent this from happening, we need a variable that can be set to false after the initial render. We can then use this variable to prevent the side effect from taking place during the initial render.
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [press, setPress] = useState(false);
const [pressed, setPressed] = useState(false);
let initialRender=true;
useEffect(() => {
if(initialRender){
initialRender=false;
} else{
setPressed(true);
}
}, [press]);
return (
<div className="App">
{pressed && <div>The button has been pressed!</div>}
<button
onClick={() => {
setPress(true);
}}
>
Click!
</button>
</div>
);
}
Preventing useEffect callback during the initial render
However, as you may have already guessed, using a variable will not solve our problem. This is because the function gets called every time the component is to be re-rendered and this will reset the variable to its initial value during every re-render.
To resolve this issue, we can use a state variable. The state values are persisted across renders as we all know. But using the state to detect the initial render will itself result in additional re-renders since a component is re-rendered every time the state is updated. There should be a neater solution.
Let’s turn this into a custom a hook
This way we can make sure the callback function is called only when the dependencies change. Now, what if we want to reuse this? Let’s turn this into a custom hook.
import { useEffect, EffectCallback, DependencyList, useRef } from "react";
/**
* This hook gets called only when the dependencies change but not during initial render.
*
* @param {EffectCallback} effect The `useEffect` callback function.
* @param {DependencyList} deps An array of dependencies.
*
* @example
* ```
* useNonInitialEffect(()=>{
* alert("Dependency changed!");
* },[dependency]);
* ```
*/
export const useNonInitialEffect = (effect: EffectCallback, deps?: DependencyList) => {
const initialRender = useRef(true);
useEffect(() => {
let effectReturns: void | (() => void | undefined) = () => {};
if (initialRender.current) {
initialRender.current = false;
} else {
effectReturns = effect();
}
if (effectReturns && typeof effectReturns === "function") {
return effectReturns;
}
}, deps);
};
This hook is syntactically similar to the useEffect hook as it accepts a callback function and an array of dependencies as arguments. But unlike the useEffect hook, the callback function doesn’t get called during the initial render.
You can find the complete code of this custom hook in this GitHub repo.