react useEffect comparing objects

JavascriptReactjsReact Hooks

Javascript Problem Overview


I am using react useEffect hooks and checking if an object has changed and only then run the hook again.

My code looks like this.

const useExample = (apiOptions) => {
	const [data, updateData] = useState([]);
	useEffect(() => {
	   const [data, updateData] = useState<any>([]);
		doSomethingCool(apiOptions).then(res => {               
           updateData(response.data);
       })
	}, [apiOptions]);

	return {
		data
	};
};

Unfortunately it keeps running as the objects are not being recognised as being the same.

I believe the following is an example of why.

const objA = {
   method: 'GET'
}

const objB = {
   method: 'GET'
}

console.log(objA === objB)

Perhaps running JSON.stringify(apiOptions) works?

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

I just found a solution which works for me.

You have to use usePrevious() and _.isEqual() from Lodash. Inside the useEffect(), you put a condition if the previous apiOptions equals to the current apiOptions. If true, do nothing. If false updateData().

Example :

const useExample = (apiOptions) => {

     const myPreviousState = usePrevious(apiOptions);
     const [data, updateData] = useState([]);
     useEffect(() => {
        if (myPreviousState && !_.isEqual(myPreviousState, apiOptions)) {
          updateData(apiOptions);
        }
     }, [apiOptions])
}

usePrevious(value) is a custom hook which create a ref with useRef().

You can found it from the Official React Hook documentation.

const usePrevious = value => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

Solution 3 - Javascript

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.

Solution 4 - Javascript

If you're real sure that you cannot control apiOptions then just replace native useEffect with https://github.com/kentcdodds/use-deep-compare-effect.

Solution 5 - Javascript

You can use useDeepCompareEffect, useCustomCompareEffect or write your own hook.

https://github.com/kentcdodds/use-deep-compare-effect https://github.com/sanjagh/use-custom-compare-effect

Solution 6 - Javascript

Supposing that a large part of your apiOptions is static, and doesn't change if the component re-render, I suggest the following approach:

const useExample = (apiOptions, deps) => {

  useEffect(callback, deps);

  return { ... };
};

The idea is to transmit only the array of values on which the effect depends as a second parameter

Solution 7 - Javascript

It's reallllly so simple in some case!

const objA = {
   method: 'GET'
}

const objB = {
   method: 'GET'
}

console.log(objA === objB) // false

Why objA not equal with objB? Coz JS just compare their address right? They are two diffirent obj. That's we all know!

The same as React hooks does!

So, also as we all know, objA.method === objB.method right? Coz they are literal.

The answer comes out:

React.useEffect(() => {
    // do your facy work
}, [obj.method])

Solution 8 - Javascript

One other option, if you have the ability to modify doSomethingCool:

If know exactly which non-Object properties you need, you can limit the list of dependencies to properties that useEffect will correctly interpret with ===, e.g.:

const useExample = (apiOptions) => {
    const [data, updateData] = useState([]);
    useEffect(() => {
       const [data, updateData] = useState<any>([]);
        doSomethingCool(apiOptions.method).then(res => {               
           updateData(response.data);
       })
    }, [apiOptions.method]);

    return {
        data
    };
};

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionpeter flanaganView Question on Stackoverflow
Solution 1 - JavascriptlenilsondcView Answer on Stackoverflow
Solution 2 - JavascriptSteffiView Answer on Stackoverflow
Solution 3 - JavascriptadlView Answer on Stackoverflow
Solution 4 - JavascriptkasongoyoView Answer on Stackoverflow
Solution 5 - JavascriptVolatore74View Answer on Stackoverflow
Solution 6 - JavascriptABDELGHANI ETTALHAOUIView Answer on Stackoverflow
Solution 7 - JavascriptA.ChanView Answer on Stackoverflow
Solution 8 - Javascriptaccidental_PhDView Answer on Stackoverflow