Reputation: 3482
I'm creating a custom hook and would like to define an optional param so that I can pass in extra dependencies when needed. My code looks like the following snippet:
import { useEffect } from 'react';
function useCustomHook(param1, extraDeps) {
useEffect(() => {
// do something with param1 here
}, [param1, ...extraDeps])
}
The react-hooks/exhaustive-deps throws a warning saying
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
Anyone have an idea about how to address that warning? Or it's not a good practice to pass deps array to custom hook?
For those who are interested in why extraDeps is needed. here's an example:
const NewComponent = (props) => {
[field1, setField1] = useState()
[field2, setField2] = useState()
// I only want this to be called when field1 change
useCustomHook('.css-selector', [field1]);
return <div>{field1}{field2}</div>;
}
Upvotes: 46
Views: 33833
Reputation: 499
I've found a useful alternative to the solutions proposed here. As mentioned in this Reddit topic, the React team apparently recommends something like:
// Pass the callback as a dependency
const useCustomHook = callback => {
useEffect(() => { /* do something */ }, [callback])
};
// Then the user wraps the callback in `useMemo` to avoid running the effect too often
// Whenever the deps change, useMemo will ensure that the callback changes, which will cause effect to re-run
useCustomHook(
useMemo(() => { /* do something */ }, [a, b, c])
);
I've used this technique and it worked very nicely.
Upvotes: 18
Reputation: 7558
The way you have defined your custom hook makes sense to me. In this case eslint cannot check your dependencies, but that does not mean that they are wrong. Simply disable the rule for this line to get rid of the warning:
function useCustomHook(param1, extraDeps) {
useEffect(() => {
// do something with param1 here
}, [param1, ...extraDeps]) // eslint-disable-line react-hooks/exhaustive-deps
}
Depending on the type of param1
, it makes sense to enable the dependency check for your custom hook by defining it in your .eslintrc.cjs
:
'react-hooks/exhaustive-deps': ['warn', {
additionalHooks: '(useCustomHook|useAnotherHook|...)'
}]
Upvotes: 4
Reputation: 3275
I had a similar issue, I wanted an effect to be executed whenever some extra dependencies were changed.
I didn't manage to give those extra dependencies, but instead I made it the way around by giving the caller the callback I wanted to be executed and let him use it when he needs.
Example :
// This hook uses extraDeps unknown by EsLint which causes a warning
const useCustomEffect = (knowDep, extraDeps) => {
const doSomething = useCallback((knownDep) => {/**/}, [])
useEffect(() => {
doSomething(knownDep)
}, [doSomething, knownDep, ...extraDeps]) // Here there is the warning
}
//Instead of this, we give the caller the callback
const useCustomEffect = (knownDep) => {
const doSomething = useCallback((knownDep) => {/**/}, [])
useEffect(() => {
doSomething(knownDep)
}, [doSomething, knownDep]) // no more warning
return { doSomething }
}
// Use it like this
const { doSomething } = useCustomEffect(foo)
useEffect(doSomething, [bar, baz]) // now I can use my callback for any known dependency
Upvotes: 5
Reputation: 1105
If you want to provide extra-deps you can use useDeepCompareEffect
instead of useEffect
.
https://github.com/kentcdodds/use-deep-compare-effect
Upvotes: 1
Reputation: 1263
I think the problem lies in how you are creating the dependency array on your custom hook. Every time you do [param1, ... extraDeps]
you are creating a new Array, so React always see them as different.
Try changing your custom hook to:
function useCustomHook(deps) {
useEffect(() => {
// do something with param1 here
}, deps)
}
And then use it like
const NewComponent = (props) => {
[field1, setField1] = useState()
[field2, setField2] = useState()
// I only want this to be called when field1 change
useCustomHook(['.css-selector', field1]);
return <div>{field1}{field2}</div>;
}
Hope it helps!
Upvotes: -2
Reputation: 31365
Here's whay you could do:
Move the state to your custom hook, run effects on it and return it.
Something like:
Component.js
function Component() {
const [field,setField] = useCustomHook(someProps);
}
useCustomHook.js
import {useState, useEffect} from 'react';
function useCustomHook(props) {
const [field,setField] = useState('');
useEffect(()=>{
// Use props received and perform effect after changes in field 1
},[field1]);
return([
field,
setField
]);
}
Upvotes: 1