Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react useEffect comparing objects

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?

like image 807
peter flanagan Avatar asked Jan 08 '19 16:01

peter flanagan


People also ask

Does useEffect do shallow comparison?

In other words, useEffect runs even though the object value(e.g. user.name ) is still the same. useEffect uses shallow equality comparison to identify whether dependencies are updated.

Does useEffect work with objects?

The objects have to be the exact same object in order for useEffect to skip running the effect. So even if the contents are the exact same, if a new object is created for the subsequent render, useEffect will re-run the effect.

What is useDeepCompareEffect?

useDeepCompareEffect is a drop-in replacement for useEffect . It will not do a reference equality check but a deep comparison before trying to run the effect. Installation per NPM/Yarn npm install use-deep-compare-effect.

Can we use 2 useEffect in React?

You can have multiple useEffects in your code and this is completely fine! As hooks docs say, you should separate concerns. Multiple hooks rule also applies to useState - you can have multiple useState in one component to separate different part of the state, you don't have to build one complicated state object.


2 Answers

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.

like image 178
lenilsondc Avatar answered Oct 25 '22 01:10

lenilsondc


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; }; 
like image 23
Steffi Avatar answered Oct 24 '22 23:10

Steffi