Reputation: 12026
In my component initialization I need to do the following steps, in this order:
If I do the following
const [myData, setMyData] = useState(null);
const [additionalObject, setAdditionalObject] = useState([]);
useEffect(async () => {
const response = await axios.get(url); // Step 1
setMyData(response.data); // Step 2
populateAdditionalObject(); // Step 3
}, []);
// --- Custom Method: ADDITIONAL STATE VAR DEPENDENT ON "myData" ---
const populteAdditionalObject = () => {
if (myData != null) {
//... set "additionalObject" as appropriate based on "myData" ...
}
}
I see that the setter in Step 2
is not immediate, and when I come to populateAdditionalObject
which checks myData
, it's not populated yet.
To solve this problem, I added a new useEffect
pointing to the variable myData
to track when it changes. I did the following:
// On initialization, fetch UserInfo
useEffect(async () => {
const response = await axios.get(url); // Step 1
setMyData(response.data); // Step 2
}, []);
// On update of myData, populate "additionalObject" state var
useEffect(() => {
populateAdditionalObject();
}, [myData]);
This does work, but now I get a warning in the browser, which I didn't have before:
Line 26:5: React Hook useEffect has a missing dependency: 'populateAdditionalObject'. Either include it or remove the dependency array react-hooks/exhaustive-deps
So how do I solve this with the right sequence and no browser warnings?
Upvotes: 0
Views: 143
Reputation: 219016
Add the function to the dependency array as the warning suggests:
useEffect(() => {
populateAdditionalObject();
}, [myData, populateAdditionalObject]);
Then to ensure that the function itself doesn't change unless it needs to, wrap it in a useCallback
with its own dependency array:
const populteAdditionalObject = useCallback(() => {
if (myData != null) {
//... set "additionalObject" as appropriate based on "myData" ...
}
}, [myData]);
Though the useEffect
dependency array may also start to complain that it has an unnecessary dependency, myData
. (Because myData
isn't actually used in the effect, at least not directly.) But since changes to myData
should result in a new instance of the callback, it should work with only that in the dependency array:
useEffect(() => {
populateAdditionalObject();
}, [populateAdditionalObject]);
Alternatively, if you don't want to chain the dependencies this much, you can tell the code linter to ignore that dependency array:
useEffect(() => {
populateAdditionalObject();
// eslint-disable-next-line
}, [myData]);
This option should be used sparingly, it's basically your way of saying "I know something about this that the linter/transpiler/etc. doesn't know, trust me." Which in this case is true, because what you know is that this effect truly should only be run when myData
changes, and that populateAdditionalObject
is always going to be re-defined on a component render and will always have the correct closures, etc.
That last part is important, and why the dependency for useCallback
included myData
. Because you don't want to call a stale populateAdditionalObject
with a stale reference to myData
because you'd end up with the same original problem you had, but more difficult to diagnose.
It's generally best to follow the linter warnings and maintain the dependency arrays completely. While this option is fine on rare occasions, more often than not I think that suppressing the warnings is covering up what should be a design change.
As a possible design change and another alternative, you could skip the second useEffect
entirely and pass the necessary data to the function:
useEffect(async () => {
const response = await axios.get(url); // Step 1
setMyData(response.data); // Step 2
populateAdditionalObject(response.data); // Step 3
// eslint-disable-next-line
}, []);
const populteAdditionalObject = (data) => {
if (data != null) {
//... set "additionalObject" as appropriate based on "data" ...
}
}
This is probably the simplest approach overall and would be preferred if the function doesn't have some other reason not to accept the data as an argument. (Something we don't see in the code shown.)
Upvotes: 1
Reputation: 64
Pass populateAdditionalObject function in the useEffect hook
useEffect(() => {
populateAdditionalObject();
}, [populateAdditionalObject]);
Upvotes: 0