The new React API includes useEffect()
, the second argument of which takes an Object
which React diffs to see if the component updated.
e.g.
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
where [props.source]
is the argument in question.
My question is: can I define a custom function to run to check if the prop has changed?
I have a custom object and React can't seem to tell when it has changed.
useEffect takes two arguments. The first argument passed to useEffect is a function called effect and the second argument (optional) is an array of dependencies. Below is an example. import { useEffect } from "react"; import { render } from "react-dom"; const App = (props) => { useEffect(() => { console.
Second argument to useEffect React compares the current value of dependency and the value on previous render. If they are not the same, effect is invoked. This argument is optional. If you omit it, effect will be executed after every render.
You can pass an empty array as the second argument to the useEffect hook to tackle this use case. useEffect(() => { // Side Effect }, []); In this case, the side effect runs only once after the initial render of the component.
useEffect(() => { const interval = setInterval(() => { console. log('This will run every second!' ); }, 1000); return () => clearInterval(interval); }, []); The code above schedules a new interval to run every second inside of the useEffect Hook.
AFAIK it's not currently possible. There are some workarounds:
1) Do deep comparison manually inside useEffect
. To store the prev. value you may use useState
or, even better, useRef
as demonstrated here: https://overreacted.io/a-complete-guide-to-useeffect/
2) Hashing with JSON.stringify(props.source)
. Can be fine, if the data is not too big. Note that stringify
may produce inconsistent results (keys in objects changing order will change the output).
3) Hashing with md5(props.source)
(or some other quick/light hashing). More realiable yet slower than the previous.
Credit for this answer goes to @Tholle use object in useEffect 2nd param without having to stringify it to JSON
const { useState, useEffect, useRef } = React;
const { isEqual } = _;
function useDeepEffect(fn, deps) {
const isFirst = useRef(true);
const prevDeps = useRef(deps);
useEffect(() => {
const isSame = prevDeps.current.every((obj, index) =>
isEqual(obj, deps[index])
);
if (isFirst.current || !isSame) {
fn();
}
isFirst.current = false;
prevDeps.current = deps;
}, deps);
}
function App() {
const [state, setState] = useState({ foo: "foo" });
useEffect(() => {
setTimeout(() => setState({ foo: "foo" }), 1000);
setTimeout(() => setState({ foo: "bar" }), 2000);
}, []);
useDeepEffect(() => {
console.log("State changed!");
}, [state]);
return <div>{JSON.stringify(state)}</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
useRef
instead of useState
By using the function returned from useState the component will be re-rendered, which is not desired in this case
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With