J'ai un cas d'utilisation de React où un composant obtient une prop category
transmise, et a également une valeur d'état local limit
. Mon comportement souhaité est:
Lorsqu'une catégorie
est sélectionnée, la limite
est réinitialisée à 10 si elle est actuellement supérieure à 10.
Lorsque la catégorie
est effacée, la limite
est réinitialisée à 50 si elle est actuellement inférieure à 50.
Tant que la catégorie
est inchangée, l'utilisateur peut choisir la limite
de son choix.
J'ai ce comportement défini dans un hook d'effet comme celui-ci (exemple plus complet en bas):
function App() { const [category, setCategory] = useState(true); return ( <div className="App"> <button onClick={() => setCategory(!category)}>Toggle category</button> <List category={category} /> </div> ); } function List({ category }) { const [limit, setLimit] = useState(10); // Change the limit when category changes // BUT only if limit is above/below a threshold! useEffect(() => { if (category && limit > 10) { setLimit(10); } else if (!category && limit < 50) { setLimit(50); } }, [category]); return ( <div> <select value={limit} onChange={e => setLimit(e.target.value)}> {[5, 10, 25, 50, 100].map(d => ( <option key={d}>{d}</option> ))} </select> <div>Category is {category ? "on" : "off"}</div> <div>Would show {limit} items</div> </div> ); }
Cela fonctionne bien, mais va à l'encontre du react-hooks / exhaust-deps linter rule qui dit que je devrais inclure limit
dans le tableau de dépendances. Je ne veux pas vraiment cela, car je ne veux pas qu'une modification de la limite
déclenchée par l'utilisateur déclenche quoi que ce soit, et je veux qu'un utilisateur puisse dépasser les seuils haut / bas pendant que le la catégorie ne change pas.
Y a-t-il une "bonne" façon d'accomplir cela qui respecte les règles des hooks tout en n'introduisant pas plusieurs hooks supplémentaires afin de jongler avec toutes les différentes transitions d'accessoires / états possibles? / p>
Exemple plus complet:
useEffect(() => { if (category && limit > 10) { setLimit(10); } else if (!category && limit < 50) { setLimit(50); } }, [category]);
3 Réponses :
Que diriez-vous d'utiliser une De cette façon, vous pouvez faire une comparaison et n'exécuter le code d'effet que si la catégorie a changé, par exemple ref
pour garder une trace de la catégorie précédente? Ils sont l'équivalent des variables d'instance a > dans les composants de fonction. const prevCat = useRef(null);
useEffect(() => {
if (prevCat.current === category) return;
prevCat.current = category;
...
}, [category, limit]);
Passez à useReducer ()
pour gérer la limite
avec la catégorie
et transmettre les deux à votre composant au lieu d'utiliser l'état local. De cette façon, le composant sera rendu uniquement lorsque l'une des valeurs change (ou les deux).
const initialState = { category: true, limit: 10 }; function reducer(state, action) { switch (action.type) { case 'toggleCategory': if (state.category) { return {category: false, limit: Math.min(50, state.limit)}; } else { return {category: true, limit: Math.max(10, state.limit)}; } case 'setLimit': return {...state, limit: action.limit}; default: throw new Error(); } } function App() { const [state, dispatch] = useReducer(reducer, initialState); // React guarantees that dispatch function identity is stable and wonât change on re-renders. // This is why itâs safe to omit from the useEffect or useCallback dependency list. const toggleCategory = useCallback(() => dispatch({type: 'toggleCategory'})); const setLimit = useCallback(limit => dispatch({type: 'setLimit', limit})); return ( <div className="App"> <button onClick={toggleCategory}>Toggle category</button> <List {...state} setLimit={setLimit} /> </div> ); } function List({ category, limit, setLimit }) { return ( <div> <select value={limit} onChange={e => setLimit(e.target.value)}> {[5, 10, 25, 50, 100].map(d => ( <option key={d}>{d}</option> ))} </select> <div>Category is {category ? "on" : "off"}</div> <div>Would show {limit} items</div> </div> ); }
Vous pouvez utiliser la version fonctionnelle de setter:
useEffect(() => { setLimit((currentLimit) => { if (category && currentLimit > 10) { return 10; } else if (!category && currentLimit < 50) { return 50; } else return currentLimit; }); }, [category]);