Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom useEffect Second Argument

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.

like image 814
Colin Ricardo Avatar asked Dec 03 '18 21:12

Colin Ricardo


People also ask

How do you pass the second argument in useEffect?

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.

Why is second argument of useEffect useful for?

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.

What do you need to add to the second argument of a useEffect hook to limit it to running only on the first 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.

How do you trigger the useEffect every second?

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.


2 Answers

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.

like image 193
Ivan Kleshnin Avatar answered Nov 02 '22 21:11

Ivan Kleshnin


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>

Why use useRef instead of useState

By using the function returned from useState the component will be re-rendered, which is not desired in this case

like image 4
Thomas Valadez Avatar answered Nov 02 '22 21:11

Thomas Valadez