4
votes

react hooks setTimeout après setState

J'ai récemment voulu concevoir un composant d'entrée avec des hooks de réaction. Le composant vérifierait la validation après avoir entré l'entrée dans 0,5 seconde.

mon code comme

const inputField = ({ name, type, hint, inputValue, setInput }) => {

    // if there is default value, set default value to state
    const [value, setValue] = useState(inputValue);

    // all of validation are true for testing
    const validCheck = () => true;

   let timeout;

    const handleChange = e => {
        const v = e.target.value;

        setValue(v);
    };

    // handle timeout
    useEffect(() => {
        let timeout;

        if (inputValue !== value) {
            timeout = setTimeout(() => {
                const valid = validCheck(value);
                console.log('fire after a moment');

                setInput({
                    key: name,
                    valid,
                    value
                });
            }, 1000);
        }

        return () => {
            clearTimeout(timeout);
        };
    });


    return (
        <SCinputField>
            <input type={type} onChange={handleChange} />
       </SCinputField>
    );
};

malheureusement, cela ne fonctionnait pas, car la variable timeout se renouvellerait à chaque fois après setValue.

J'ai trouvé que les react-hooks fournissent une fonctionnalité comme useRef pour stocker la variable.

Dois-je l'utiliser ou ne devrais pas utiliser react-hooks dans ce cas?

Mettre à jour

ajouter useEffect

const inputField = ({ name, type, hint, inputValue, setInput }) => {

    // if there is default value, set default value to state
    const [value, setValue] = useState(inputValue);

    // all of validation are true for testing
    const validCheck = () => true;

   let timeout;

    const handleChange = e => {
        clearTimeout(timeout);
        const v = e.target.value;

        setValue(v);

        timeout = setTimeout(() => {
            // if valid
            if (validCheck()) {
               // do something...
            }
        }, 500);
    };

    return (
        <SCinputField>
            <input type={type} onChange={handleChange} />
       </SCinputField>
    );
};

Cela semble fonctionner, mais je ne suis pas sûr que ce soit une bonne façon d'utiliser.


1 commentaires

utilisez plutôt useEffect


3 Réponses :


-1
votes

Vous pouvez déplacer la variable de délai d'expiration dans la méthode handleChange .

const inputField = ({ name, type, hint, inputValue, setInput }) => {

// if there is default value, set default value to state
const [value, setValue] = useState(inputValue);

// all of validation are true for testing
const validCheck = () => true;

const handleChange = e => {


    let timeout;
    clearTimeout(timeout);
    const v = e.target.value;

    setValue(v);

    timeout = setTimeout(() => {
        // if valid
        if (validCheck()) {
           // do something...
        }
    }, 500);
};

return (
    <SCinputField>
        <input type={type} onChange={handleChange} />
   </SCinputField>
);

};


2 commentaires

Désolé, j'essaye ceci et montre console.log, mais j'ai les heures que j'ai entrées. Peut-être que clearTimeout ne fonctionne pas bien.


C'est parce que la variable timeout ne vit que dans l'instance de la fonction handleChange . Sur un nouveau rendu du composant, handleChange sera une nouvelle fonction, qui a sa propre variable timeout , sans rien savoir de la précédente. Ainsi, clearTimeout (timeout) n'a aucun effet, le timeout défini précédemment sera toujours déclenché.



19
votes

Voici comment procéder:

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id="root"/>

EXEMPLE DE FONCTIONNEMENT SUR SNIPPET CI-DESSOUS:

function InputField() {

  const [value, setValue] = React.useState('');
  const timeoutRef = React.useRef(null);

  function validate() {
    console.log('Validating after 500ms...');
  }

  React.useEffect(() => {
    if (timeoutRef.current !== null) {
      clearTimeout(timeoutRef.current);
    }
     
    timeoutRef.current = setTimeout(()=> {
      timeoutRef.current = null;
      value !== '' ? validate() : null;
    },500);
  },[value]);
  
  return(
    <input type='text' value={value} onChange={(e) => setValue(e.target.value)}/>
  );
  
}

ReactDOM.render(<InputField/>, document.getElementById('root'));
import React, {useState, useEffect, useRef} from 'react';

function InputField() {

  const [value, setValue] = useState('');       // STATE FOR THE INPUT VALUE
  const timeoutRef = useRef(null);              // REF TO KEEP TRACK OF THE TIMEOUT

  function validate() {                         // VALIDATE FUNCTION
    console.log('Validating after 500ms...');
  }

  useEffect(() => {                             // EFFECT TO RUN AFTER CHANGE IN VALUE
    if (timeoutRef.current !== null) {          // IF THERE'S A RUNNING TIMEOUT
      clearTimeout(timeoutRef.current);         // THEN, CANCEL IT
    }

    timeoutRef.current = setTimeout(()=> {      // SET A TIMEOUT
      timeoutRef.current = null;                // RESET REF TO NULL WHEN IT RUNS
      value !== '' ? validate() : null;         // VALIDATE ANY NON-EMPTY VALUE
    },500);                                     // AFTER 500ms
  },[value]);                                   // RUN EFFECT AFTER CHANGE IN VALUE

  return(                                       // SIMPLE TEXT INPUT
    <input type='text' 
      value={value} 
      onChange={(e) => setValue(e.target.value)}
    />
  );

}


3 commentaires

Merci, mais timeoutRef.current = null dans setTimeout est nécessaire?


Vous avez raison. Ce n'est pas nécessaire. Parce que vous pouvez réinitialiser le délai d'expiration sur la même variable sans aucun problème. Je viens de modifier cette ligne et cela fonctionne bien! Merci


En fait, j'ai rajouté cette ligne. Je pense que c'est une bonne pratique de remettre votre référence à zéro lorsque vous avez fini de l'utiliser dans ce cas. Bien sûr, cela fonctionne sans cela. Mais en procédant de cette manière, vous pouvez vérifier if (timeoutRef.current) et vous obtiendrez false si votre délai d'expiration est déjà exécuté. Si vous ne le définissez pas sur null, je pense que vous obtiendrez true même si votre délai d'expiration est déjà exécuté.



4
votes

Vous n'avez pas besoin de conserver la référence au délai entre les rendus. Vous pouvez simplement renvoyer une fonction du useEffect pour l'effacer:

const validate = useCallback((value) => {
  // do something with the `value` state
  if ( /* value is NOT valid */ ) {
    onError(); // call the props for an error
  } else {
    onValid();
  }
}, [onError, onValid]); // and any other dependencies your function may use

De plus, n'oubliez pas de transmettre toutes les dépendances à l'effet, y compris le fonction de validation .

Idéalement, vous passeriez la valeur comme paramètre à la fonction de validation: validate (valeur) - ceci manière, la fonction a moins de dépendances, et pourrait même être pure et déplacée en dehors du composant.

Alternativement, si vous avez des dépendances internes (comme un autre setState ou un onError callback depuis props ), créez la fonction validate avec un hook useCallback () :

React.useEffect(() => {
  const timeout = setTimeout(()=> {
    if (value !== '') {
      validate();
    }
  }, 500);

  return () => {
    clearTimeout(timeout);  // this guarantees to run right before the next effect
  }
},[value, validate]);


0 commentaires