web-dev-qa-db-fra.com

réagir useEffect comparer des objets

J'utilise des crochets react useEffect et je vérifie si un objet a changé, puis je réexécute le crochet.

Mon code ressemble à ceci.

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

    return {
        data
    };
};

Malheureusement, il continue de fonctionner car les objets ne sont pas reconnus comme étant les mêmes.

Je crois que ce qui suit est un exemple de pourquoi.

const objA = {
   method: 'GET'
}

const objB = {
   method: 'GET'
}

console.log(objA === objB)

Peut-être que l'exécution de JSON.stringify(apiOptions) fonctionne?

36
peter flanagan

Utilisez apiOptions comme valeur d'état

Je ne sais pas comment vous utilisez le hook personnalisé, mais faire de apiOptions une valeur d'état en utilisant useState devrait fonctionner très bien. De cette façon, vous pouvez le servir à votre hook personnalisé en tant que valeur d'état comme ceci:

const [apiOptions, setApiOptions] = useState({ a: 1 })
const { data } = useExample(apiOptions)

De cette façon, cela ne changera que lorsque vous utiliserez setApiOptions.

Exemple # 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>;
}

Alternativement

Vous pouvez écrire un useEffect profondément comparable comme décrit ici :

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, useDeepCompareMemoize(dependencies))
}

Vous pouvez l'utiliser comme vous utiliseriez useEffect.

20
lenilsondc

Je viens de trouver une solution qui fonctionne pour moi.

Vous devez utiliser usePrevious() et _.isEqual() de Lodash. A l'intérieur de la useEffect(), vous mettez une condition si la précédente apiOptions est égale à la actuel apiOptions. Si vrai, ne faites rien. Si faux updateData ().

Exemple :

const useExample = (apiOptions) => {

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

usePrevious(value) est un hook personnalisé qui crée un ref avec useRef().

Vous pouvez le trouver dans le Official React Hook documentation .

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

Si vous êtes vraiment sûr que vous ne pouvez pas contrôler apiOptions, remplacez simplement useEffect natif par https://github.com/kentcdodds/use-deep-compare-effect .

2
kasongoyo