Tengo problemas para manejar una lista con useState en React. Tengo que agregar un elemento a la lista dos veces seguidas y solo se agrega el segundo elemento. El código:
Aplicación.js
import React, { useState } from "react"; import ItemForm from "./itemForm"; function App() { const [itemList, setItemList] = useState([]); function addItem(item) { setItemList([...itemList, item]); } return ( <div> <ItemForm onAddItem={ addItem } /> { itemList && itemList.map((item, i) => <ul key={ i + itemList } >{ item }</ul>) } </div> ); } export default App;ItemForm.js
import React, { useState } from 'react'; function ItemForm({ onAddItem }) { const [inputValue, setInputValue] = useState(''); function handleSubmit(e) { e.preventDefault(); var item = e.target.item.value; // Here, I have to call onAddItem twice. onAddItem(item); onAddItem(item + " second time"); } return ( <div> <form onSubmit={ handleSubmit } > <input value={ inputValue } onChange={(e) => setInputValue(e.target.value) } name="item"></input> <button type="submit">Add</button> </form> </div> ) } export default ItemForm;Este es el comportamiento real de la aplicación:
Y este es el comportamiento esperado :
¿Por qué sucede esto y cómo puedo solucionarlo?
Las actualizaciones de estado son asíncronas
Para sus dos llamadas, la matriz itemsList es exactamente la misma. :
Suponga que tiene una matriz: [1,2,3] . Lo que esencialmente hace su código es hacerlo [1,2,3,4] por primera vez y [1,2,3,5] por segunda vez.
setItemList([...itemList, item]) //[1,2,3,4] setItemList([...itemList, item]) //[1,2,3,5]Su matriz en la segunda actualización todavía usa el valor original.
Una forma de hacer esto puede usar este patrón que usa el estado anterior. Esta es una forma confiable de usar el estado anterior.
import React, { useState } from "react"; import ItemForm from "./itemForm"; function App() { const [itemList, setItemList] = useState([]); function addItem(item) { setItemList(itemList => ([...itemList, item])); } return ( <div> <ItemForm onAddItem={ addItem } /> { itemList && itemList.map((item, i) => <ul key={ i + itemList } >{ item }</ul>) } </div> ); } export default App; import React, { useState } from 'react'; function ItemForm({ onAddItem }) { const [inputValue, setInputValue] = useState(''); function handleSubmit(e) { e.preventDefault(); var item = e.target.item.value; // Here, I have to call onAddItem twice. onAddItem(item); onAddItem(item + " second time"); } return ( <div> <form onSubmit={ handleSubmit } > <input value={ inputValue } onChange={(e) => setInputValue(e.target.value) } name="item"></input> <button type="submit">Add</button> </form> </div> ) } export default ItemForm;La función pasada al componente ItemForm como accesorio, es decir, onAddItem , tiene un cierre sobre el valor de itemList que estaba en vigor cuando se creó esa función.
Como resultado, llamar a esa función dos veces con un valor diferente para item crea una nueva matriz con el mismo valor de itemList que cerró la función addItem .
Ejemplo:
Si la función addItem pasa al componente ItemForm tiene un cierre sobre el valor inicial de itemList , es decir, una matriz vacía; entonces la siguiente llamada a la función de establecimiento de estado:
setItemList([...itemList, item]); extiende la matriz vacía y luego agrega el nuevo item como se muestra a continuación.
setItemList([...[], item]); Llamar a la función addItem dos veces es lo mismo que:
setItemList([...[], item]); setItemList([...[], item]);También tenga en cuenta que el estado es constante dentro de una representación particular de un componente. El componente no puede ver el estado actualizado hasta que se vuelve a renderizar.
Puede pasar una función a setItemList para actualizar el estado en función del valor anterior de itemList :
function addItem(item) { setItemList(currentState => [...currentState, item]); } La opción alternativa es pasar una matriz a la función onAddItem :
onAddItem([item1, item2]); y cambie la función addItem como:
function addItem(itemsArr) { setItemList([...itemList, ...itemsArr]); }React no actualiza el estado inmediatamente después de llamar a setState , toma todas las llamadas a setState y las ejecuta antes de volver a procesarlas.
Entonces, en su caso, el segundo onAddItem está anulando el primero.
Para resolver tales problemas, debe cambiar algo de lógica en su código, por ejemplo:
function addItem(...newItems) { setItemList(previousItemList => ([...previousItemList, ...newItems])); }Ahora puede pasar varios elementos para agregar un elemento y llamarlo solo una vez.
onAddItem(item, item + " second time");