Me gustaría que el usuario pueda ordenar una lista de elementos pendientes. Cuando los usuarios seleccionan un elemento de un menú desplegable, establecerán el sortKey
que creará una nueva versión setSortedTodos
y, a su vez, activará useEffect
y llamará setSortedTodos
.
El siguiente ejemplo funciona exactamente como quiero, sin embargo, eslint me está pidiendo que agregue todos
al useEffect
conjunto de dependencias, y si lo hago, causa un bucle infinito (como era de esperar).
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]);
Ejemplo en vivo:
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>
Estoy pensando que tiene que haber una mejor manera de hacer esto que mantenga feliz a Eslint.
sort
devolución de llamada puede ser justa: loreturn a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
que también tiene la ventaja de hacer una comparación local si el entorno tiene información local razonable. Si lo desea, también puede lanzarle desestructuración: pastebin.com/7X4M1XTHeslint
lanzando?[<>]
botón de la barra de herramientas)? Los fragmentos de pila admiten React, incluido JSX; He aquí cómo hacer uno . De esa manera, las personas pueden verificar que sus soluciones propuestas no tengan el problema del bucle infinito ...todos
a la matriz de dependenciasuseEffect
, y puede ver por qué no debería hacerlo. :-)Respuestas:
Yo diría que esto significa que hacerlo de esta manera no es lo ideal. La función depende de hecho
todos
. SisetTodos
se llama a otro lugar, la función de devolución de llamada debe ser recalculada, de lo contrario, funciona con datos obsoletos.¿Por qué almacena la matriz ordenada en estado de todos modos? Puede usar
useMemo
para ordenar los valores cuando cambia la clave o la matriz:Luego referencia en
sortedTodos
todas partes.Ejemplo en vivo:
Mostrar fragmento de código
No es necesario almacenar los valores ordenados en estado, ya que siempre puede derivar / calcular la matriz ordenada a partir de la matriz "base" y la clave de ordenación. Yo diría que también hace que su código sea más fácil de entender, ya que es menos complejo.
fuente
useMemo
. Solo una pregunta secundaria, ¿por qué no usar.localCompare
en el género?La razón del bucle infinito es porque todos no coinciden con la referencia anterior y el efecto se volverá a ejecutar.
¿Por qué usar un efecto para una acción de clic de todos modos? Puede ejecutarlo en una función como esta:
y en tu menú desplegable haz un
onChange
.Nota sobre la dependencia por cierto, ¡ESLint tiene razón! Sus Todos, en el caso descrito anteriormente, son una dependencia y deberían estar en la lista. El enfoque en la selección de un artículo es incorrecto, y de ahí su problema.
fuente
data.slice(0)
crea la copiasetState
ya que no editará el objeto existente y por lo tanto lo clonará internamente. Redacción incorrecta en mi respuesta, cierto. Yo editaré esto.setState
no clona datos. ¿Por qué piensas eso?setState
no clona nada. Felix y el OP son correctos, debe copiar la matriz antes de ordenarla.state
.Lo que debe hacer aquí es usar una forma funcional de
setState
:Códigos de trabajo y caja
Incluso si está copiando el estado para no mutar el original, todavía no se garantiza que obtendrá su último valor, debido a que el estado de configuración es asíncrono. Además, la mayoría de los métodos devolverán una copia superficial, por lo que puede terminar mutando el estado original de todos modos.
El uso de funcional
setState
garantiza que obtenga el último valor del estado y no mute el valor del estado original.fuente
.sort
cambia la matriz en el lugar, por lo que aún tendrá que copiarla usted mismo.