Tengo un problema en el que una función setState actualiza dos valores de estado separados.
Estoy tratando de hacer una pequeña matriz ordenable de React. El programa debería comportarse así:
unsortedData y datos displayedDatadisplayedData se ordena (usando .sort() )displayedData para que tengan el mismo valor que unsortedData , restaurando el orden original Sin embargo, al hacer clic por primera vez en alternar la ordenación, ordena tanto los datos unsortedData como los datos displayedData , lo que significa que pierdo el orden de los datos originales. Entiendo que podría almacenar su pedido, pero quiero saber por qué estos valores de estado parecen estar acoplados.
Código de trabajo de Stackblitz aquí .
GIF que muestra un problema aquí
No puedo encontrar dónde está sucediendo esto. No puedo ver ninguna referencia de objeto/matriz en curso (me estoy extendiendo para nuevos objetos/matrices).
Código aquí:
const Test = () => { const [unsortedData, setUnsortedData] = useState([]) const [displayedData, setDisplayedData] = useState([]) const [isSorted, setisSorted] = useState(false) const handleSorting = () => setisSorted(!isSorted) useEffect(() => { if (isSorted === true) setDisplayedData([...unsortedData.sort()]) // sort data if (isSorted === false) setDisplayedData([...unsortedData]) // restore original data order }, [isSorted, unsortedData]) useEffect(() => { const mockData = [3, 9, 6] setUnsortedData([...mockData]) // store original data order in "unsortedData" setDisplayedData([...mockData]) }, []) return ( <div> {displayedData.map(item => item)} <br /> <button onClick={() => handleSorting()}>Toggle sorting</button> </div> ) }La función .sort muta la matriz, así que cuando haces esto:
setDisplayedData([...unsortedData.sort()])Estás mutando datos sin clasificar y luego haciendo una copia de ellos. Dado que mutó la matriz original, ese cambio puede mostrarse en la pantalla cuando el componente se vuelve a renderizar.
Entonces, una solución mínima sería copiar primero y ordenar después:
setDisplayedData([...unsortedData].sort())Además, ¿por qué necesito el useEffect? ¿Por qué no puedo simplemente mover el contenido de useEffect (que tiene las declaraciones if) a la función handleSorting?
Moverlo a handleSorting debería ser posible, pero en realidad me gustaría proponer otra opción: tener solo dos estados, los datos sin clasificar y el valor booleano de ordenarlos. Los datos mostrados son entonces un valor calculado basado en esos dos.
const [unsortedData, setUnsortedData] = useState([3, 9, 6]) // initialize with mock data const [isSorted, setisSorted] = useState(false) const displayedData = useMemo(() => { if (isSorted) { return [...unsortedData].sort(); } else { return unsortedData } }, [unsortedData, isSorted]); const handleSorting = () => setisSorted(!isSorted) // No use useEffect to sort the data // Also no useEffect for the mock data, since i did that when initializing the stateLos beneficios de este enfoque son que
Si desea mostrar una matriz ordenada al usuario solo cuando el usuario presiona el botón, puede usar esta declaración de código (originalmente copiada de la lista de tareas pendientes de vue3 )
const filters = { none: (data) => [...data], sorted: (data) => [...data].sort((a, b) => a - b), } const Test = () => { const [unsortedData, setUnsortedData] = useState([]) const [currentFilter, setCurrentFilter] = useState('none') useEffect(() => { const mockData = [3, 9, 6] setUnsortedData([...mockData]) }, []) return ( <div> <ul> {/* All magic is going here! */} {filters[currentFilter](unsortedData).map(item => <li key={item}>{item}</li>)} </ul> <br /> <button onClick={() => setCurrentFilter(currentFilter === 'none' ? 'sorted' : 'none')}> Toggle sorting </button> </div> ) }