Reputation: 37026
I want to make custom hook that will skip first useEffect render and reuse it everywhere.
I made a working example of custom hook:
function useEffectSkipFirst(fn, arr) {
const isFirstRun = useRef(true);
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
fn();
}, [...arr]);
}
How to use:
useEffectSkipFirst(fn, []);
Problem is that i got a few warnings that i try to understand and hope you can help me:
React Hook useEffect has a missing dependency: 'fn'. Either include it or remove the dependency array. If 'fn' changes too often, find the parent component that defines it and wrap that definition in useCallback. (react-hooks/exhaustive-deps) eslint
React Hook useEffect has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies. (react-hooks/exhaustive-deps)
Full example code:
import React, { useRef, useEffect, useState } from "react";
import ReactDOM from "react-dom";
function useEffectSkipFirst(fn, arr) {
const isFirstRun = useRef(true);
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
fn();
}, [...arr]);
}
function App() {
const [clicks, setClicks] = useState(0);
const [date, setDate] = useState(Date.now());
const [useEffectRunTimes, setTseEffectRunTimes] = useState(0);
useEffectSkipFirst(() => {
addEffectRunTimes();
}, [clicks, date]);
function addEffectRunTimes() {
setTseEffectRunTimes(useEffectRunTimes + 1);
}
return (
<div className="App">
<div>clicks: {clicks}</div>
<div>date: {new Date(date).toString()}</div>
<div>useEffectRunTimes: {useEffectRunTimes}</div>
<button onClick={() => setClicks(clicks + 1)}>add clicks</button>
<button onClick={() => setDate(Date.now())}>update date</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Upvotes: 2
Views: 1774
Reputation: 18173
I see one solution to your problem.
First you need to reduce the array of useEffect dependencies to a single value (by using the hook useMemo
).
Then in the effect, you should use useRef
to store the old value of this dependency value, so you will be able to call the fn
function only when the dependency has changed.
Here is the code of the custom hook :
function useEffectSkipFirst(fn, depValue) {
const oldDepValueRef = useRef(depValue);
useEffect(() => {
if (depValue !== oldDepValueRef.current) {
// Note that this code will not be executed the first time as
// we initialized oldDepValueRef with depValue
fn();
oldDepValueRef.current = depValue;
}
}, [fn, depValue]);
}
Here is the code to reduce the array dep to a single value using useMemo
:
// I decided the return an object but it could be an array or something else
const depValue = useMemo(() => ({}), [clicks, date]);
useEffectSkipFirst(addEffectRunTimes, depValue);
function addEffectRunTimes() {
// When the value depends on the previous value,
// you can use a function to update the state value
setTseEffectRunTimes(v => v + 1);
}
By the way if the addEffectRunTimes
function is only used in the effect, you could directly write :
useEffectSkipFirst(() => setTseEffectRunTimes(v => v + 1), depValue);
Upvotes: 1
Reputation: 10873
The first warning basically means that all the dependencies referenced inside useEffect
's callback have to be declared inside its dependencies array. The official documentation provides more explanation about adding functions to the dependency array.
The second waring means that you can't specify dependencies dynamically, i.e. in a way which makes them not known until runtime. This is because the dependencies are compared statically before code execution and therefore have to be spelled out explicitly.
Upvotes: 1
Reputation: 282040
The fn function which you take as a callback to useEffectSkipFirst
changes on every render. Also since this function is being called within useEffectSkipFirst
, the linter prompts you to define it as a dependency.
However, the way you implement it, its upto the code that implements useEffectSkipFirst
to take care that all values have the correct closures and you can skip defining it as a dependency to useEffect
. If however you do, it might lead to an infinite loop.
You may check this post too for more details: How to fix missing dependency warning when using useEffect React Hook?
Upvotes: 1