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.
3 Réponses :
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> );
};
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é.
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)} /> ); }
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é.
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]);
utilisez plutôt
useEffect