Issue
There are several other SO questions on this where the answer is either to eliminate the dependencies complaints via ESLint (I’m using typescript) or to do something else to still allow the second parameter of useEffect to be []
. However per the React docs this is not recommended. Also under the react useEffect docs it says
If you pass an empty array ([]), the props and state inside the effect
will always have their initial values. While passing [] as the second
argument is closer to the familiar componentDidMount and
componentWillUnmount mental model, there are usually better solutions
to avoid re-running effects too often. Also, don’t forget that React
defers running useEffect until after the browser has painted, so doing
extra work is less of a problem.
I have the following code:
useEffect(() => {
container.current = new VisTimeline(container.current, items, groups, options);
}, [groups, items, options]);
I want it to run only one time.
Is the only way around this to let it run each time and useState
to track it has ran before like this:
const [didLoad, setDidLoad] = useState<boolean>(false);
useEffect(() => {
if (!didLoad) {
container.current = new VisTimeline(container.current, items, groups, options);
setDidLoad(true);
}
}, [didLoad, groups, items, options]);
Solution
The way I handle this now is to put the appropriate dependencies in the list of dependencies.
Because I want the effect to run only one time, and because the effect only relies on some data when the component first mounts, it’s perfectly fine to omit those dependencies. For example, the groups
prop may change later, but this effect doesn’t need to run again.
But, as a habit I don’t omit the recommended dependencies and I always list them. If I were to intentionally omit something, I would add an eslint ignore statement… it’s whatever convention you want to follow as long as you understand what is happening when that data changes and the effect does / does not run.
However the code I proposed, shown below, isn’t the best solution if you do want to list the dependencies as it causes an extra render when didLoad
changes.
const [didLoad, setDidLoad] = useState<boolean>(false);
useEffect(() => {
if (!didLoad) {
container.current = new VisTimeline(container.current, items, groups, options);
setDidLoad(true);
}
}, [didLoad, groups, items, options]);
Instead of using state to track that the effect ran, I will use a ref (which doesn’t need to be a dependency).
const timelineLoaded = useRef<boolean>(false);
useEffect(() => {
if (!timelineLoaded.current) {
container.current = new VisTimeline(container.current, items, groups, options);
timelineLoaded.current = true;
}
}, [groups, items, options]);
Answered By – Diesel
Answer Checked By – Clifford M. (BugsFixing Volunteer)