With useEffect, how can I skip applying an effect upon the initial render?
As the guide states,
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
In this example from the guide it's expected that count
is 0 only on initial render:
const [count, setCount] = useState(0);
So it will work as componentDidUpdate
with additional check:
useEffect(() => {
if (count)
document.title = `You clicked ${count} times`;
}, [count]);
This is basically how custom hook that can be used instead of useEffect
may work:
function useDidUpdateEffect(fn, inputs) {
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current) {
return fn();
}
didMountRef.current = true;
}, inputs);
}
Credits go to @Tholle for suggesting useRef
instead of setState
.
Here's a custom hook that just provides a boolean flag to indicate whether the current render is the first render (when the component was mounted). It's about the same as some of the other answers but you can use the flag in a useEffect
or the render function or anywhere else in the component you want. Maybe someone can propose a better name.
import { useRef, useEffect } from 'react';
export const useIsMount = () => {
const isMountRef = useRef(true);
useEffect(() => {
isMountRef.current = false;
}, []);
return isMountRef.current;
};
You can use it like:
import React, { useEffect } from 'react';
import { useIsMount } from './useIsMount';
const MyComponent = () => {
const isMount = useIsMount();
useEffect(() => {
if (isMount) {
console.log('First Render');
} else {
console.log('Subsequent Render');
}
});
return isMount ? <p>First Render</p> : <p>Subsequent Render</p>;
};
And here's a test for it if you're interested:
import { renderHook } from '@testing-library/react-hooks';
import { useIsMount } from '../useIsMount';
describe('useIsMount', () => {
it('should be true on first render and false after', () => {
const { result, rerender } = renderHook(() => useIsMount());
expect(result.current).toEqual(true);
rerender();
expect(result.current).toEqual(false);
rerender();
expect(result.current).toEqual(false);
});
});
Our use case was to hide animated elements if the initial props indicate they should be hidden. On later renders if the props changed, we did want the elements to animate out.
I found a solution that is more simple and has no need to use another hook, but it has drawbacks.
useEffect(() => {
// skip initial render
return () => {
// do something with dependency
}
}, [dependency])
This is just an example that there are others ways of doing it if your case is very simple.
The drawback of doing this is that you can't have a cleanup effect and will only execute when the dependency array changes the second time.
This isn't recommended to use and you should use what the other answers are saying, but I only added this here so people know that there is more than one way of doing this.
Edit:
Just to make it more clear, you shouldn't use this approach to solving the problem in the question (skipping the initial render), this is only for teaching purpose that shows you can do the same thing in different ways. If you need to skip the initial render, please use the approach on other answers.