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.
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.
reactjs - How can I deep compare objects/arrays in useEffect/React? - Stack Overflow
Does useEffect compare deeply?
React useEffect() : Most efficient way to compare if two arrays of objects are equal
Does React apply a shallow/deep compare in hooks's dependency array?
It would be easier to use use-deep-compare-effect library, it provides a hook like useEffects, but it compares the dependent objects deeply with their previous values.
import useDeepCompareEffect from 'use-deep-compare-effect';
...
useDeepCompareEffect(() => {
// The codes that were in the useEffects previously
}, [dependentObject]);
Also, here is the link to this library:
https://github.com/kentcdodds/use-deep-compare-effect
Based on your comment, you can do something like this:
const [config, setConfig] = useState(undefined);
const [pluginData, setPluginData] = useState(undefined);
useEffect(() => {
if(!_.isEqual(config, props.settings.config) && !_.isEqual(pluginData, props.pluginData)) {
//do sth with new props
// Update old props to have them actual for the next comparison
setConfig(props.settings.config);
setPluginData(props.pluginData);
}
}, [props.settings.config, props.pluginData])
Basically, you can have a state of the old props and then deep compare them.
What is the most efficient way
The most efficient way is likely a custom compare for this particular shape of data. You could use JSON.stringify or some other generic comparing function, but it's not likely to be the most efficient for obvious reasons.
So in your example a simple compare function might look like ->
const array1 = [{id:1,title:'one'},{id:2,title:'two'},{id:3,title:'three'}];
const array2 = [{id:1,title:'one'},{id:2,title:'two'},{id:3,title:'four'}];
const arrayIsEqual = (a1, a2) =>
a1 === a2 ||
a1.length === a2.length &&
a1.every((f,i) =>
f.id === a2[i].id &&
f.title === a2[i].title
)
//tests
const a1clone = [...array1];
//different arrays not equal
console.log(arrayIsEqual(array1, array2)); //false
//different arrays equal
console.log(arrayIsEqual(array1, a1clone)); //true
//different arrays same values, but different order
a1clone.reverse();
console.log(arrayIsEqual(array1, a1clone)); //false
//same arrays, fastest check
console.log(arrayIsEqual(array1, array1)); //true
First of all 1k objects is not a large number of objects, but getting back to the question: the easiest way is to use
JSON.stringify(object1) === JSON.stringify(object2);
But it is not the most efficient, when performance is an issue, the smartest thing I could think of is to use a meta property like version and whenever we update the object in any way we increment the version as well.
Then all you have to do is to check two arrays based on id + version mapping.
If the second option is not desired you could use lodash.deepCompare or create your own function, takea look at How to make data input readonly, but showing calendar?
and simply check object by object while iterating.