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)])
Answer from Shubham Khatri on Stack OverflowInstead 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)])
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])
useEffect() doesn't trigger on property object change (Deep object)
What if you need to check an object in the useEffect dependency array?
reactjs - useEffect React Hook: detecting the change of a property for each object in an array - Stack Overflow
react useEffect comparing objects - javascript
I have an object that looks something like this stored in a Redux store:
[ layer1: false, layer2: true, layer3: false]
These flags represent layers that are shown or hidden on a Leaflet map.
The various fields are toggled by their associated inputs and in the component which consumes them, I need to add or remove the specified layers when the object above changes.
Since you can't pass non-primitives into the useEffect dependency array, I considered trying the following I found on SO:
useEffect(() => {
// ...
), [JSON.stringify(myLayersObject)])
This doesn't work, I think because it's effectively passing a constant in the dependency list.
I am also considering using a byte to store the flags and doing bitwise manipulation to check the values. That should work, but it may be somewhat unclear to other devs what I am doing, so any alternative suggestions are welcome.
The usual remedy of trying to extract only the primitive property you care about doesn't work, because i need to care about all of them. In the real code there are only layers, and I suppose I could make 6 useEffect hooks with dependency arrays like [myLayersObject.layer1], etc. but that is also ugly.
Any suggestions?
Thanks.
Use apiOptions as state value
I'm not sure how you are consuming the custom hook but making apiOptions a state value by using useState should work just fine. This way you can serve it to your custom hook as a state value like so:
const [apiOptions, setApiOptions] = useState({ a: 1 })
const { data } = useExample(apiOptions)
This way it's going to change only when you use setApiOptions.
Example #1
import { useState, useEffect } from 'react';
const useExample = (apiOptions) => {
const [data, updateData] = useState([]);
useEffect(() => {
console.log('effect triggered')
}, [apiOptions]);
return {
data
};
}
export default function App() {
const [apiOptions, setApiOptions] = useState({ a: 1 })
const { data } = useExample(apiOptions);
const [somethingElse, setSomethingElse] = useState('default state')
return <div>
<button onClick={() => { setApiOptions({ a: 1 }) }}>change apiOptions</button>
<button onClick={() => { setSomethingElse('state') }}>
change something else to force rerender
</button>
</div>;
}
Alternatively
You could write a deep comparable useEffect as described here:
function deepCompareEquals(a, b){
// TODO: implement deep comparison here
// something like lodash
// return _.isEqual(a, b);
}
function useDeepCompareMemoize(value) {
const ref = useRef()
// it can be done by using useMemo as well
// but useRef is rather cleaner and easier
if (!deepCompareEquals(value, ref.current)) {
ref.current = value
}
return ref.current
}
function useDeepCompareEffect(callback, dependencies) {
useEffect(
callback,
dependencies.map(useDeepCompareMemoize)
)
}
You can use it like you'd use useEffect.
If the input is shallow enough that you think deep equality would still be fast, consider using JSON.stringify:
const useExample = (apiOptions) => {
const [data, updateData] = useState([]);
const apiOptionsJsonString = JSON.stringify(apiOptions);
useEffect(() => {
const apiOptionsObject = JSON.parse(apiOptionsJsonString);
doSomethingCool(apiOptionsObject).then(response => {
updateData(response.data);
})
}, [apiOptionsJsonString]);
return {
data
};
};
Note it won’t compare functions.
"use client";
import { useEffect, useState } from "react";
import Header from "./header";
export default function Layout({ children }: { children: React.ReactNode }) {
useEffect(() => { console.log('changed'); }, [children]);
return (
<>
<Header />
<div>{children}</div>
</>
);
}