React hooks - trigger useEffect when a nested property changes in a collection of objects
Asked Answered
F

5

43

A page displays a list of objects [{name:, age:}, ...] A second page allows to update the name of a particular object. Then using hooks, how should I implement useEffect() so the list on the front page updates only when a name change has been detected?

const [objects, setObjects] = useState([]);

useEffect(()=> {
  getAllObjects()
},[getAllObjects, objects]);
Fronniah answered 6/5, 2019 at 18:26 Comment(1)
Read what Dan Abramov has to say regarding ESLINT for such situationsDislocate
S
37

Instead of passing the entire object to the dependency array, make sure that you only pass name. Which you can do by returning the names

const [objects, setObjects] = useState([])

useEffect(()=> {
      getAllObjects()
}, [getAllObjects, ...objects.map(item => item.name)])
Shandeigh answered 6/5, 2019 at 18:42 Comment(12)
Thanks, I am glad I asked I wouldnt have figured this trick out myself. Is it considered best practice though? The create-react-app linter raises a warning: 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. Do you know if I can get rid of it without disabling the linting ?Fronniah
Maybe use objects.map(item => item.name).concat([getAllObjects]) for dependency arrayShandeigh
Ok thanks, I tried... the linter then complained the expression is too complex and I should extract it to a variable... what I did and then pass the variable to the deps array... now it's telling me that the variable is a unnecessary dep... there might be a flaw in my design here...Fronniah
@Fronniah Since you know what you are doing, you can disable the lint rule for this particular lineShandeigh
Ok thanks, I'll start with that! Still looking forward to finding an answer that would follow Hooks best practices.... If it exists!Fronniah
In my case, I needed an object, ended up with: ...Object.keys(myObj).map(key => myObj[key])Gamboge
Object.keys(objects).map(key => ${key}_${objects[key]}).join("_") this give you a single string which always going to be different when key or value changes in the object. Works well.Ambrosial
Really gr8 one, Thanks a lotClientage
Excellent answer!Tithonus
Question is about efficiency, the spread & map will run on every render. It is better to manually extract the properties and only then supply them to the dep arrayDislocate
@keif, an alternative to extract object values: ...Object.values(myObj)Rockaway
@Rockaway too right! Crazy how I stumbled on Object.keys and then ignored Object.values as giving me the same output with less effort.Gamboge
E
7

Check https://dev.to/aileenr/til-you-can-watch-for-nested-properties-changing-in-react-s-useeffect-hook-26nj

One can just do:

useEffect(()=> {
      // do something
}, [values.name])

It's a fine solution if the object property always exists however if the property is not present at some point you get a reference error. A workaround in this scenario is to check if prop exists inside the hook

useEffect(()=> {
    if (values?.name) {
        // do something
    }
}, [values])
Endblown answered 23/11, 2021 at 10:20 Comment(1)
the question was how to declare dependency to a property in an array/collection... (ie. in ember.js you would do it like [email protected])Adabelle
A
3
// using JSON.stringify(object)
useEffect(() => {
   // your code here...
}, [JSON.stringify(dependencyObject)]);

The best solution is to use JSON.stringify(object) as it won't lead to any error on the initial load or warning about the changes in size of dependency variables.


// using spread operator
useEffect(() => {
   // your code here...
}, [ ...Object.values(dependencyObject) ]);

The solution with spread operator on object keys and values will cause an error if the object is null/undefined in the initial load.

/* 
  Also if you make a custom function that either returns the 
  values or empty array then React will give a warning about 
  the size change in dependency array.
*/
const getDependencies = (addressType: Address) => {
if (addressType) {
  return Object.values(addressType);
}
return [];
}

useEffect(() => {
   // your code here...
}, [ ...getDependencies(dependencyObject) ]);

So use JSON.stringyfy(object). it won't give any errors when the object is undefined or null and React won't complain about the change in size of dependency variables.

Agnella answered 10/8, 2023 at 11:49 Comment(1)
which is more efficient if the object will never be null?Garibay
O
1

None of the previous answers worked for me

This is my solution:

const [objects, setObjects] = useState([]);

useEffect(()=> {
  getAllObjects()
},[getAllObjects, ...Object.values(objects)]);

This way you are extracting the values from your state and once any of these values change the useEffect will be triggered

Obstacle answered 11/8, 2022 at 3:24 Comment(0)
D
-5

Please add JSON.stringify for the nested object.

useEffect(()=> {
   getAllObjects()
},[getAllObjects, JSON.stringify(objects)]);
Dyslogia answered 22/3, 2022 at 8:9 Comment(1)
dependency array accepts variables not valuesFronnia

© 2022 - 2024 — McMap. All rights reserved.