Skip to content Skip to sidebar Skip to footer

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 subs

Solution 1:

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.

Solution 2:

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 } = _;

functionuseDeepEffect(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);
}

functionApp() {
  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"));
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script><scriptsrc="https://unpkg.com/react@16/umd/react.development.js"></script><scriptsrc="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script><divid="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

Solution 3:

Taking inspiration from other answers, I've been using the following hook

importReactfrom'react';

importIsEqualfrom'lodash/isEqual';

/**
 * The role of this function is to ensure that useEffect doesn't not
 * fire unless the value of a varr (variable but "var" is a reserved word 😉)
 * changes. For examples:
 * const a = { 'hi': 5 }
 * const b = { 'hi': 5 }
 *
 * a === b // false
 * IsEqual(a, b) // true
 *
 * By default the React.useEffect(() => {}, [dependencies]) only does "===" but
 * for cases where we only want useEffect to re-fire when the true value
 * of a varr changes "IsEqual", we use this.
 *
 * @param varr: any
 * @returns {function(): *}
 */constuseMostRecentImmutableValue = (varr) => {
  let mostRecentValue = varr;
  const mostRecentPointer = React.useRef(varr);

  return() => {
    // short circuit if "shallow equality"if (mostRecentPointer.current === varr) {
      return mostRecentValue;
    }

    // mostRecentValue only updates when the true value of varr changesif (!IsEqual(varr, mostRecentPointer.current)) {
      mostRecentValue = varr;
    }

    // mostRecentPointer changes every time "shallow equality" fails but// after we've checked deep equality
    mostRecentPointer.current = varr;

    return mostRecentValue;
  };
};

exportdefault useMostRecentImmutableValue;

And then in a hook I would do something like this:

import useMostRecentImmutableValue from'use-most-recent-immutable-value.hook';

constuseMyHook = (foo = { 'hi': 5 }) => {
  const mostRecentFoo = useMostRecentImmutableValue(foo);

  React.useEffect(() => {
    const unsubscribe = mySubscriptionFunction(mostRecentFoo);
    return() => {
      unsubscribe();
    };
  }, [mostRecentFoo]);

};

exportdefault useMyHook;

Post a Comment for "Custom Useeffect Second Argument"