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.
Why is the dependency array important in useEffect?
Can you use objects or arrays in the useEffect dependency array?
What happens if you don’t pass a dependency array to useEffect?
Videos
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.