Custom useEffect Second Argument

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

import React from 'react';

import IsEqual from '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(): *}
 */
const useMostRecentImmutableValue = (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 changes
    if (!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;
  };
};

export default useMostRecentImmutableValue;

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

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

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

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

};

export default useMyHook;

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>

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