web-dev-qa-db-fra.com

Exécuter du code asynchrone lors de la mise à jour de l'état avec react-hooks

J'ai quelque chose comme:

const [loading, setLoading] = useState(false);

...

setLoading(true);
doSomething(); // <--- when here, loading is still false. 

La définition de l'état est toujours asynchrone, alors quel est le meilleur moyen d'attendre la fin de cet appel setLoading()?

La setLoading() ne semble pas accepter de rappel comme setState().

un exemple

basé sur la classe

getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (this.state.pagesSeen.includes(this.state.page + 1)) {
      return this.setState({
        page: this.state.page + 1,
      });
    }

    if (this.state.prefetchedOrders) {
      const allOrders = this.state.orders.concat(this.state.prefetchedOrders);
      return this.setState({
        orders: allOrders,
        page: this.state.page + 1,
        pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
        prefetchedOrders: null,
      });
    }

    this.setState(
      {
        isLoading: true,
      },
      () => {
        getOrders({
          page: this.state.page + 1,
          query: this.state.query,
          held: this.state.holdMode,
          statuses: filterMap[this.state.filterBy],
        })
          .then((o) => {
            const { orders } = o.data;
            const allOrders = this.state.orders.concat(orders);
            this.setState({
              orders: allOrders,
              isLoading: false,
              page: this.state.page + 1,
              pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
              // Just in case we're in the middle of a prefetch.
              prefetchedOrders: null,
            });
          })
          .catch(e => console.error(e.message));
      },
    );
  };

convertir en fonction

  const getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (pagesSeen.includes(page + 1)) {
      return setPage(page + 1);
    }

    if (prefetchedOrders) {
      const allOrders = orders.concat(prefetchedOrders);
      setOrders(allOrders);
      setPage(page + 1);
      setPagesSeen([...pagesSeen, page + 1]);
      setPrefetchedOrders(null);
      return;
    }

    setIsLoading(true);

    getOrders({
      page: page + 1,
      query: localQuery,
      held: localHoldMode,
      statuses: filterMap[filterBy],
    })
      .then((o) => {
        const { orders: fetchedOrders } = o.data;
        const allOrders = orders.concat(fetchedOrders);

        setOrders(allOrders);
        setPage(page + 1);
        setPagesSeen([...pagesSeen, page + 1]);
        setPrefetchedOrders(null);
        setIsLoading(false);
      })
      .catch(e => console.error(e.message));
  };

Dans ce qui précède, nous voulons exécuter chaque appel setWei séquentiellement. Cela signifie-t-il que nous devons configurer de nombreux points d'ancrage useEffect différents pour reproduire ce problème?

22
Colin Ricardo

useState setter ne fournit pas de rappel après la mise à jour de l'état comme le fait setState dans React composants de classe. Pour reproduire le même comportement, vous pouvez utiliser le modèle similaire comme la méthode componentDidUpdate cycle de vie dans React composants de classe avec useEffect à l'aide de crochets

useEffect hooks prend le deuxième paramètre comme un tableau de valeurs que React doit surveiller les modifications après la fin du cycle de rendu).

const [loading, setLoading] = useState(false);

...

useEffect(() => {
    doSomething(); // This is be executed when `loading` state changes
}, [loading])
setLoading(true);

EDIT

Contrairement à setState, le programme de mise à jour de useState hook n'a pas de rappel, mais vous pouvez toujours utiliser un useEffect pour répliquer le comportement ci-dessus. Cependant, vous devez déterminer le changement de chargement

L’approche fonctionnelle de votre code ressemblerait à

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

const prevLoading = usePrevious(isLoading);

useEffect(() => {
   if (!prevLoading && isLoading) {
       getOrders({
          page: page + 1,
          query: localQuery,
          held: localHoldMode,
          statuses: filterMap[filterBy],
      })
      .then((o) => {
        const { orders: fetchedOrders } = o.data;
        const allOrders = orders.concat(fetchedOrders);

        setOrders(allOrders);
        setPage(page + 1);
        setPagesSeen([...pagesSeen, page + 1]);
        setPrefetchedOrders(null);
        setIsLoading(false);
      })
      .catch(e => console.error(e.message));
   }
}, [isLoading, preFetchedOrders, orders, page, pagesSeen]);

const getNextPage = () => {
    // This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
    goToTop();

    if (pagesSeen.includes(page + 1)) {
      return setPage(page + 1);
    }

    if (prefetchedOrders) {
      const allOrders = orders.concat(prefetchedOrders);
      setOrders(allOrders);
      setPage(page + 1);
      setPagesSeen([...pagesSeen, page + 1]);
      setPrefetchedOrders(null);
      return;
    }

    setIsLoading(true);
  };
18
Shubham Khatri

Attendez la restitution de votre composant.

const [loading, setLoading] = useState(false);

useEffect(() => {
    if (loading) {
        doSomething();
    }
}, [loading]);

setLoading(true);

Vous pouvez améliorer la clarté avec quelque chose comme:

function doSomething() {
  // your side effects
  // return () => {  }
}

function useEffectIf(condition, fn) {
  useEffect(() => condition && fn(), [condition])
}

function App() {
  const [loading, setLoading] = useState(false);
  useEffectIf(loading, doSomething)

  return (
    <>
      <div>{loading}</div>
      <button onClick={() => setLoading(true)}>Click Me</button>
    </>
  );
}
8
Federkun

Je ne sais pas quel est exactement le cas d'utilisation ici, mais je pense que nous pouvons réaliser les actions asynchrones en créant un hook personnalisé.

ci-dessous est pour référence

const useAPI =(initState)=>{
    const [custState,changeCustState] = useState(initState);
    return ([custState,(newState,accept a callback here )=>{
    //console.log(newState);
    /// execute the call back ()
    return changeCustState(newState);}]);
};

...

const [loading, setLoading] = useAPI(false);
0
Debasish Jena