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 sortKeyque creará una nueva versión setSortedTodosy, a su vez, activará useEffecty llamará setSortedTodos.
El siguiente ejemplo funciona exactamente como quiero, sin embargo, eslint me está pidiendo que agregue todosal useEffectconjunto 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.
                 
                
                
                 
sortdevolució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/7X4M1XTHeslintlanzando?[<>]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 ...todosa 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. SisetTodosse 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
useMemopara ordenar los valores cuando cambia la clave o la matriz:Luego referencia en
sortedTodostodas 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.localCompareen 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 copiasetStateya que no editará el objeto existente y por lo tanto lo clonará internamente. Redacción incorrecta en mi respuesta, cierto. Yo editaré esto.setStateno clona datos. ¿Por qué piensas eso?setStateno 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
setStategarantiza que obtenga el último valor del estado y no mute el valor del estado original.fuente
.sortcambia la matriz en el lugar, por lo que aún tendrá que copiarla usted mismo.