Tengo una aplicación de lista de tareas nativa de reacción. Los elementos de la tarea están representados por una matriz usando useState que se declara de la siguiente manera:
const [taskItems, setTaskItems] = useState([]); Estoy tratando de agregar una función que elimine todos los elementos, pero en lugar de configurarlo así setTaskItems([]) , que simplemente elimina todos los elementos a la vez, esperaba eliminar los elementos uno a la vez a partir del final para crear un aspecto más animado.
while(taskItems.length > 0) { alert(taskItems.length); let itemsCopy = [...taskItems]; itemsCopy.splice(-1); setTaskItems(itemsCopy); //setTimeout(function(){},180); }Por alguna razón, el código anterior provoca una recursividad infinita. Si elimino el bucle while, la función deleteAll elimina el último elemento de la lista. Basado en la colocación de la alerta al comienzo de la función, parece que la longitud de la matriz nunca disminuye.
¿Porqué es eso?
¿Y hay una mejor manera de lograr este efecto?
Sucede porque en su código el valor de taskItem no cambia cuando usa setTaskItems
setTaskItems es una función asíncrona y el valor actualizado de la propiedad de estado cambia después de la representación del componente
puedes intentar algo como esto
const [taskItems, setTaskItems] = useState([]); const [deleteAll, setDeleteAll] = useState(false) useEffect(() => { if(!deleteAll) return; if(taskItems.lenght === 0){ setDeleteAll(false) return; } setTimeout(() => { setTaskItems(t => t.slice(0, -1)) }, 180) }, [deleteAll, taskItems]) const handleDeleteAll = () => { seDeleteAll(true) }Esto se debe a que el valor de taskItems se recupera del cierre, lo que significa que no puede esperar que el valor cambie durante la ejecución del controlador. El cierre se recrea solo después de que el elemento se vuelve a representar, lo que significa que puede acceder al nuevo valor de estado solo en la siguiente ejecución de la función; durante la ejecución de la función, el valor del estado nunca cambia.
Solo necesita asignar un valor de longitud a alguna variable temporal y usarla como indicador de inicio/parada para el ciclo while:
let itemsCount = taskItems.length; while(itemsCount > 0) { // Here use itemsCount to determine how much you would need to slice taskItems array itemsCount -= 1; }ACTUALIZADO
Una idea de cómo deberías hacerlo es:
const [items, setItems] = useState([1, 2, 3, 4, 5]); const deleteAll = () => { let counter = items.length; while (counter > 0) { setTimeout(() => { setItems((p) => [...p.slice(0, counter - 1)]); }, counter * 1000); counter -= 1; } };Es importante multiplicar el retraso del tiempo de espera con la duración, para evitar la ejecución simultánea de todos los tiempos de espera; debe eliminarlos uno por uno. También debe borrar el tiempo de espera al desmontar o algo así.
Debido a la naturaleza asíncrona del enlace useState de React, debe aprovechar useEffect para hacer el trabajo. Aquí hay un ejemplo práctico de cómo eliminar todas las tareas con un retraso de 1000 ms entre elementos.
function TasksGrid() { const [taskItems, setTaskItems] = useState([1, 2, 3, 4, 5, 6]); const [runDeleteAll, setRunDeleteAll] = useState(false); useEffect( () => { if (runDeleteAll) { if (taskItems.length > 0) setTimeout(() => setTaskItems(taskItems.slice(0, taskItems.length - 1)), 1000); else setRunDeleteAll(false); } }, [runDeleteAll, taskItems] ) const handleOnClick = () => { setRunDeleteAll(true); } return ( <div> {taskItems.map((taskItem, idx) => <p key={idx}>task {taskItem}</p>)} <button onClick={handleOnClick}>delete all</button> </div> ); }