J'aimerais que l'utilisateur puisse trier une liste de tâches. Lorsque les utilisateurs sélectionnent un élément dans une liste déroulante, il définit le sortKey
qui créera une nouvelle version de setSortedTodos
, et déclenchera à son tour l' useEffect
appel et setSortedTodos
.
L'exemple ci-dessous fonctionne exactement comme je le souhaite, mais eslint m'invite à ajouter todos
au useEffect
tableau de dépendance, et si je le fais, cela provoque une boucle infinie (comme vous vous en doutez).
const [todos, setTodos] = useState([]);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
const cloned = data.slice(0);
const sorted = cloned.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
setTodos(sorted);
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos]);
Exemple en direct:
const {useState, useCallback, useEffect} = React;
const exampleToDos = [
{title: "This", priority: "1 - high", text: "Do this"},
{title: "That", priority: "1 - high", text: "Do that"},
{title: "The Other", priority: "2 - medium", text: "Do the other"},
];
function Example() {
const [todos, setTodos] = useState(exampleToDos);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
const cloned = data.slice(0);
const sorted = cloned.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
setTodos(sorted);
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos]);
const sortByChange = useCallback(e => {
setSortKey(e.target.value);
});
return (
<div>
Sort by:
<select onChange={sortByChange}>
<option selected={sortKey === "title"} value="title">Title</option>
<option selected={sortKey === "priority"} value="priority">Priority</option>
</select>
{todos.map(({text, title, priority}) => (
<div className="todo">
<h4>{title} <span className="priority">{priority}</span></h4>
<div>{text}</div>
</div>
))}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
body {
font-family: sans-serif;
}
.todo {
border: 1px solid #eee;
padding: 2px;
margin: 4px;
}
.todo h4 {
margin: 2px;
}
.priority {
float: right;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
Je pense qu'il doit y avoir une meilleure façon de faire cela qui garde eslint heureux.
sort
rappel peut être juste:return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
ce qui a également l'avantage de comparer les paramètres régionaux si l'environnement dispose d'informations régionales raisonnables. Si vous le souhaitez, vous pouvez également lancer la déstructuration: pastebin.com/7X4M1XTHeslint
lance?[<>]
bouton de la barre d'outils)? Les extraits de pile prennent en charge React, y compris JSX; voici comment en faire un . De cette façon, les gens peuvent vérifier que leurs solutions proposées n'ont pas le problème de boucle infinie ...todos
au tableau de dépendancesuseEffect
, et vous pouvez voir pourquoi vous ne devriez pas. :-)Réponses:
Je dirais que cela signifie que procéder ainsi n'est pas idéal. La fonction est en effet dépendante de
todos
. SisetTodos
est appelé ailleurs, la fonction de rappel doit être recalculée, sinon elle fonctionne sur des données périmées.Pourquoi stockez-vous le tableau trié dans l'état de toute façon? Vous pouvez utiliser
useMemo
pour trier les valeurs lorsque la clé ou le tableau change:Puis référence
sortedTodos
partout.Exemple en direct:
Afficher l'extrait de code
Il n'est pas nécessaire de stocker les valeurs triées dans l'état, car vous pouvez toujours dériver / calculer le tableau trié à partir du tableau "de base" et de la clé de tri. Je dirais que cela rend également votre code plus facile à comprendre car il est moins complexe.
la source
useMemo
. Juste une question secondaire, pourquoi ne pas utiliser.localCompare
dans le genre?La raison de la boucle infinie est que todos ne correspond pas à la référence précédente et que l'effet se réexécutera.
Pourquoi utiliser quand même un effet pour un clic? Vous pouvez l'exécuter dans une fonction comme celle-ci:
et sur votre liste déroulante, faites un
onChange
.Remarque sur la dépendance d'ailleurs, ESLint a raison! Vos Todos, dans le cas décrit ci-dessus, sont une dépendance et doivent figurer dans la liste. L'approche sur la sélection d'un article est erronée, et donc votre problème.
la source
data.slice(0)
crée la copie.setState
car il ne modifiera pas l'objet existant et le clonera donc en interne. Mauvais libellé dans ma réponse, vrai. Je vais éditer ça.setState
ne clone pas les données. Pourquoi pensez-vous cela?setState
ne clone rien. Felix et l'OP sont corrects, vous devez copier le tableau avant de le trier.state
.Ce que vous devez faire ici est d'utiliser une forme fonctionnelle de
setState
:Codes et boîte de travail
Même si vous copiez l'état afin de ne pas muter l'original, il n'est toujours pas garanti que vous obtiendrez sa dernière valeur, car le paramètre étant asynchrone. De plus, la plupart des méthodes renverront une copie superficielle, vous pourriez donc finir par muter l'état d'origine de toute façon.
L'utilisation de la fonctionnalité
setState
garantit que vous obtenez la dernière valeur de l'état et que vous ne modifiez pas la valeur de l'état d'origine.la source
.sort
modifie le tableau en place, vous devrez donc le copier vous-même.