web-dev-qa-db-fra.com

Équivalent à composantDidUpdate à l'aide des crochets React

tldr; Comment simuler componentDidUpdate ou utiliser autrement key avec un tableau pour forcer la réinitialisation de mon composant?

J'implémente un composant qui affiche une minuterie et exécute un rappel lorsqu'il atteint zéro. Le rappel vise à mettre à jour une liste d'objets. Ce dernier composant est constitué des nouveaux crochets ReactuseState et useEffect

La state contient une référence à l'heure à laquelle le minuteur a été démarré et au temps restant. La effect définit un intervalle appelé toutes les secondes pour mettre à jour le temps restant et vérifier si le rappel doit être appelé.

Le composant n'est pas destiné à reprogrammer un minuteur, ni à conserver l'intervalle lorsqu'il atteint zéro, il est supposé exécuter le rappel et l'inactivité. Pour que le minuteur s'actualise, j'espérais passer un tableau à key, ce qui entraînerait la réinitialisation de l'état du composant et le redémarrage du minuteur. Malheureusement, key doit être utilisé avec une chaîne. Par conséquent, le fait que la référence de mon tableau ait été modifiée ne produit aucun effet.

J'ai également essayé de pousser les modifications sur les accessoires en passant le tableau qui me préoccupait, mais l'état a été maintenu et donc l'intervalle n'a pas été réinitialisé.

Quelle serait la méthode préférée pour observer les changements superficiels dans un tableau afin de forcer la mise à jour d'un état à l'aide de la nouvelle API de hooks?

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';

function getTimeRemaining(startedAt, delay) {
    const now = new Date();
    const end = new Date(startedAt.getTime() + delay);
    return Math.max(0, end.getTime() - now.getTime());
}

function RefresherTimer(props) {
    const [startedAt, setStartedAt] = useState(new Date());
    const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));

    useEffect(() => {

        if (timeRemaining <= 0) {
            // The component is set to idle, we do not set the interval.
            return;
        }

        // Set the interval to refresh the component every second.
        const i = setInterval(() => {
            const nowRemaining = getTimeRemaining(startedAt, props.delay);
            setTimeRemaining(nowRemaining);

            if (nowRemaining <= 0) {
                props.callback();
                clearInterval(i);
            }
        }, 1000);

        return () => {
            clearInterval(i);
        };
    });

    let message = `Refreshing in ${Math.ceil(timeRemaining / 1000)}s.`;
    if (timeRemaining <= 0) {
        message = 'Refreshing now...';
    }

    return <div>{message}</div>;
}

RefresherTimer.propTypes = {
    callback: PropTypes.func.isRequired,
    delay: PropTypes.number
};

RefresherTimer.defaultProps = {
    delay: 2000
};

export default RefresherTimer;

Tentative d'utilisation avec key:

<RefresherTimer delay={20000} callback={props.updateListOfObjects} key={listOfObjects} />

Tentative d'utilisation avec un changement d'accessoire:

<RefresherTimer delay={20000} callback={props.updateListOfObjects} somethingThatChanges={listOfObjects} />

listOfObjects fait référence à un tableau d'objets, les objets eux-mêmes ne changeant pas nécessairement, le tableau doit donc être comparé à !==. En règle générale, la valeur proviendra de Redux, où l'action updateListOfObjects provoque la réinitialisation du tableau, comme suit: newListOfObjects = [...listOfObjects].

5
FMCorz

La withRef crée une "variable d'instance" dans le composant fonctionnel. Il agit comme un indicateur pour indiquer s'il est en phase de montage ou de mise à jour sans mise à jour.

const mounted = useRef();
useEffect(() => {
  if (!mounted.current) {
    mounted.current = true;
  } else {
    // do componentDidUpate logic
  }
});
1
Morgan Cheng

En bref, vous voulez réinitialiser votre minuterie lorsque la référence du tableau change, non??. Si c'est le cas, vous devrez utiliser un mécanisme de différenciation, une solution basée uniquement sur les hooks tirerait parti du second paramètre de useEffect, comme alors:

function RefresherTimer(props) {
    const [startedAt, setStartedAt] = useState(new Date());
    const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));

    //reset part, lets just set startedAt to now
    useEffect(() => setStartedAt(new Date()),
      //important part
      [props.listOfObjects] // <= means: run this effect only if any variable
      // in that array is different from the last run
    )

    useEffect(() => {
    // everything with intervals, and the render
}

Plus d'informations sur ce comportement ici https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

1
Bear-Foot

Une façon de remonter un composant consiste à fournir une nouvelle propriété key. Ce n'est pas nécessairement une chaîne, mais une chaîne sera forcée en interne. Par conséquent, si listOfObjects est une chaîne, il est prévu que key soit comparé en interne à listOfObjects.toString().

Toute clé aléatoire peut être utilisée, par exemple uuid ou Math.random(). Une comparaison superficielle de listOfObjects peut être effectuée dans le composant parent pour fournir une nouvelle clé. useMemo hook peut être utilisé dans l'état parent pour mettre à jour de manière conditionnelle la clé de remontage, et listOfObjects peut être utilisé en tant que liste de paramètres devant être mémorisés. Voici un exemple :

  const remountKey = useMemo(() => Math.random(), listOfObjects);

  return (
    <div>
      <RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
    </div>
  );

Au lieu de remonter la clé, le composant enfant pourrait être en mesure de réinitialiser son propre état et d'exposer un rappel pour déclencher une réinitialisation.

Effectuer une comparaison superficielle de listOfObjects à l'intérieur du composant enfant constituerait un anti-modèle, car cela impliquerait qu'il soit au courant de l'implémentation du composant parent.

0
estus