Modificación correcta de matrices de estado en ReactJS

417

Quiero agregar un elemento al final de una statematriz, ¿es esta la forma correcta de hacerlo?

this.state.arrayvar.push(newelement);
this.setState({arrayvar:this.state.arrayvar});

Me preocupa que la modificación de la matriz in situ pushpueda causar problemas, ¿es seguro?

La alternativa de hacer una copia de la matriz, e setStateing parece un desperdicio.

fadedbee
fuente
99
Creo que puedes usar ayudantes de reacción de inmutabilidad. mira esto: facebook.github.io/react/docs/update.html#simple-push
nilgun
setState en estado matriz comprueba mi solución. <br/> <br> stackoverflow.com/a/59711447/9762736
Rajnikant Lodhi

Respuestas:

763

Los documentos de React dicen:

Trate este estado como si fuera inmutable.

Su pushva a mutar el estado directamente y que podría potencialmente conducir al código de error en decúbito prono, incluso si está "Reposición" el estado de nuevo después. F.ex, podría llevar a que algunos métodos de ciclo de vida como componentDidUpdateno se activen.

El enfoque recomendado en versiones posteriores de React es usar una función de actualización al modificar estados para evitar condiciones de carrera:

this.setState(prevState => ({
  arrayvar: [...prevState.arrayvar, newelement]
}))

El "desperdicio" de memoria no es un problema en comparación con los errores que podría enfrentar al usar modificaciones de estado no estándar.

Sintaxis alternativa para versiones anteriores de React

Puede usar concatpara obtener una sintaxis limpia ya que devuelve una nueva matriz:

this.setState({ 
  arrayvar: this.state.arrayvar.concat([newelement])
})

En ES6 puede usar el Operador Spread :

this.setState({
  arrayvar: [...this.state.arrayvar, newelement]
})
David Hellsing
fuente
8
¿Puedes dar un ejemplo de cuándo ocurriría una condición de carrera?
soundly_typed
3
@Qiming pushdevuelve la nueva longitud de la matriz para que no funcione. Además, setStatees asíncrono y React puede poner en cola varios cambios de estado en un solo pase de renderizado.
David Hellsing
2
@mindeavor dice que tiene un animationFrame que busca parámetros en this.state y otro método que cambia algunos otros parámetros en el cambio de estado. Podría haber algunos marcos donde el estado ha cambiado pero no se refleja en el método que escucha el cambio, porque setState es asíncrono.
David Hellsing
1
@ChristopherCamps Esta respuesta no alienta a llamar setStatedos veces, muestra dos ejemplos similares de configuración de matriz de estado sin mutarla directamente.
David Hellsing
2
Una forma fácil de tratar una matriz de estado como inmutable en estos días es: let list = Array.from(this.state.list); list.push('woo'); this.setState({list});modificar sus preferencias de estilo, por supuesto.
basicdays
133

Más fácil, si lo estás usando ES6.

initialArray = [1, 2, 3];

newArray = [ ...initialArray, 4 ]; // --> [1,2,3,4]

Nueva matriz será [1,2,3,4]

para actualizar tu estado en React

this.setState({
         arrayvar:[...this.state.arrayvar, newelement]
       });

Obtenga más información sobre la desestructuración de matrices

Apátrida
fuente
@Muzietto, ¿puedes elaborar?
Menos
44
El quid de esta pregunta es cambiar el estado de Reacción, no modificar las matrices. Usted vio mi punto por sí mismo y editó su respuesta. Esto hace que su respuesta sea relevante. Bien hecho.
Marco Faustinelli
2
Sus preguntas no se refieren directamente a la pregunta de OP
StateLess
1
@ChanceSmith: también se necesita en StateLess answer. No dependa en la actualización del estado del estado mismo. Documento oficial: reactjs.org/docs/…
arcol
1
@RayCoder registra y verifica el valor de arrayvar, parece que no es una matriz.
StateLess
57

La forma más sencilla con ES6:

this.setState(prevState => ({
    array: [...prevState.array, newElement]
}))
Ridd
fuente
Lo siento, en mi caso quiero insertar una matriz en una matriz. tableData = [['test','test']]Después empujó mi nueva matriz tableData = [['test','test'],['new','new']]. cómo impulsar esto @David y @Ridd
Johncy
@Johncy Si desea [['test','test'],['new','new']]probar:this.setState({ tableData: [...this.state.tableData, ['new', 'new']]
Ridd el
this.setState({ tableData: [...this.state.tableData ,[item.student_name,item.homework_status_name,item.comments===null?'-':item.comments] ] });Inserta la nueva matriz dos veces. this.state.tableData.push([item.student_name,item.homework_status_name,item.comments===null?'-':item.comments]);Logra lo que quiero. pero no es la forma correcta, creo.
Johncy
26

React puede realizar actualizaciones por lotes y, por lo tanto, el enfoque correcto es proporcionar a setState una función que realice la actualización.

Para el complemento React update, lo siguiente funcionará de manera confiable:

this.setState( state => update(state, {array: {$push: [4]}}) );

o para concat ():

this.setState( state => ({
    array: state.array.concat([4])
}));

A continuación se muestra lo que https://jsbin.com/mofekakuqi/7/edit?js,output como un ejemplo de lo que sucede si se equivoca.

La invocación setTimeout () agrega correctamente tres elementos porque React no procesará actualizaciones por lotes dentro de una devolución de llamada setTimeout (consulte https://groups.google.com/d/msg/reactjs/G6pljvpTGX0/0ihYw2zK9dEJ ).

El buggy onClick solo agregará "Third", pero el fijo agregará F, S y T como se esperaba.

class List extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      array: []
    }

    setTimeout(this.addSome, 500);
  }

  addSome = () => {
      this.setState(
        update(this.state, {array: {$push: ["First"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Second"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Third"]}}));
    };

  addSomeFixed = () => {
      this.setState( state => 
        update(state, {array: {$push: ["F"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["S"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["T"]}}));
    };



  render() {

    const list = this.state.array.map((item, i) => {
      return <li key={i}>{item}</li>
    });
       console.log(this.state);

    return (
      <div className='list'>
        <button onClick={this.addSome}>add three</button>
        <button onClick={this.addSomeFixed}>add three (fixed)</button>
        <ul>
        {list}
        </ul>
      </div>
    );
  }
};


ReactDOM.render(<List />, document.getElementById('app'));
NealeU
fuente
¿Existe realmente un caso en el que sucede? Si simplemente lo hacemosthis.setState( update(this.state, {array: {$push: ["First", "Second", "Third"]}}) )
Albizia
1
@Albizia Creo que deberías encontrar un colega y discutirlo con ellos. No hay ningún problema de procesamiento por lotes si solo realiza una llamada setState. El punto es mostrar que React procesa actualizaciones de lotes, así que sí ... realmente hay un caso, que es lo que puedes encontrar en la versión JSBin del código anterior. Casi todas las respuestas en este hilo no abordan esto, por lo que habrá un montón de código que a veces sale mal
NealeU
state.array = state.array.concat([4])esto muta el objeto de estado anterior.
Emile Bergeron
1
@EmileBergeron Gracias por su persistencia. Finalmente miré hacia atrás y vi lo que mi cerebro se negaba a ver, y revisé los documentos, así que editaré.
NealeU
1
¡Bueno! Es realmente fácil equivocarse ya que la inmutabilidad en JS no es obvia (aún más cuando se trata con la API de una biblioteca).
Emile Bergeron
17

Como @nilgun mencionó en el comentario, puede usar los ayudantes de reacción de inmutabilidad . He encontrado que esto es súper útil.

De los documentos:

Empuje simple

var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]

initialArray sigue siendo [1, 2, 3].

Clarkie
fuente
66
Los ayudantes de React inmutability se describen como obsoletos en la documentación. github.com/kolodny/immutability-helper ahora debería usarse en su lugar.
Periata Breatta
3

Si está utilizando un componente funcional, utilícelo de la siguiente manera.

const [chatHistory, setChatHistory] = useState([]); // define the state

const chatHistoryList = [...chatHistory, {'from':'me', 'message':e.target.value}]; // new array need to update
setChatHistory(chatHistoryList); // update the state
usuario3444748
fuente
1

Para agregar un nuevo elemento a la matriz, push()debería ser la respuesta.

Para eliminar el elemento y actualizar el estado de la matriz, el siguiente código funciona para mí. splice(index, 1)no puede trabajar.

const [arrayState, setArrayState] = React.useState<any[]>([]);
...

// index is the index for the element you want to remove
const newArrayState = arrayState.filter((value, theIndex) => {return index !== theIndex});
setArrayState(newArrayState);
Jagger
fuente
0

Estoy tratando de impulsar el valor en un estado de matriz y establecer un valor como este y definir la matriz de estado y el valor de inserción por función de mapa.

 this.state = {
        createJob: [],
        totalAmount:Number=0
    }


 your_API_JSON_Array.map((_) => {
                this.setState({totalAmount:this.state.totalAmount += _.your_API_JSON.price})
                this.state.createJob.push({ id: _._id, price: _.your_API_JSON.price })
                return this.setState({createJob: this.state.createJob})
            })
Rajnikant Lodhi
fuente
0

Esto funcionó para mí para agregar una matriz dentro de una matriz

this.setState(prevState => ({
    component: prevState.component.concat(new Array(['new', 'new']))
}));
MadKad
fuente
0

Aquí hay un ejemplo de 2020, Reactjs Hook que pensé que podría ayudar a otros. Lo estoy usando para agregar nuevas filas a una tabla Reactjs. Avíseme si pudiera mejorar algo.

Agregar un nuevo elemento a un componente de estado funcional:

Defina los datos del estado:

    const [data, setData] = useState([
        { id: 1, name: 'John', age: 16 },
        { id: 2, name: 'Jane', age: 22 },
        { id: 3, name: 'Josh', age: 21 }
    ]);

Hacer que un botón active una función para agregar un nuevo elemento

<Button
    // pass the current state data to the handleAdd function so we can append to it.
    onClick={() => handleAdd(data)}>
    Add a row
</Button>
function handleAdd(currentData) {

        // return last data array element
        let lastDataObject = currentTableData[currentTableData.length - 1]

        // assign last elements ID to a variable.
        let lastID = Object.values(lastDataObject)[0] 

        // build a new element with a new ID based off the last element in the array
        let newDataElement = {
            id: lastID + 1,
            name: 'Jill',
            age: 55,
        }

        // build a new state object 
        const newStateData = [...currentData, newDataElement ]

        // update the state
        setData(newStateData);

        // print newly updated state
        for (const element of newStateData) {
            console.log('New Data: ' + Object.values(element).join(', '))
        }

}
Ian Smith
fuente
-1
this.setState({
  arrayvar: [...this.state.arrayvar, ...newelement]
})
M.Samiei
fuente
2
Agregue alguna explicación para evitar esta publicación para considerar como publicación de baja calidad stackoverflow.
Harsha Biyani
2
@Kobe esta llamada "operador de propagación" en ES6 y agregue elementos de array2 a array1. gracias por el
voto negativo
@ M.Samiei Debes editar tu publicación para evitar votos negativos. Es de baja calidad como solo un fragmento de código. Además, es peligroso modificar el estado de esta manera, ya que las actualizaciones pueden estar agrupadas, por lo que la forma preferida es, por ejemplosetState(state => ({arrayvar: [...state.arrayvar, ...newelement]}) );
NealeU
y difundir el newelementobjeto dentro de una matriz arroja un de TypeErrortodos modos.
Emile Bergeron
-1
//------------------code is return in typescript 

const updateMyData1 = (rowIndex:any, columnId:any, value:any) => {

    setItems(old => old.map((row, index) => {
        if (index === rowIndex) {
        return Object.assign(Object.assign({}, old[rowIndex]), { [columnId]: value });
    }
    return row;
}));
ANUBHAV RAWAT
fuente
-6

Este código funciona para mí:

fetch('http://localhost:8080')
  .then(response => response.json())
  .then(json => {
    this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
  })
Hamid Hosseinpour
fuente
2
aún así,
mutas el
2
Y no actualiza el estado del componente correctamente, ya que .pushdevuelve un número , no una matriz.
Emile Bergeron