Modifica corretta delle matrici di stato in ReactJS


417

Voglio aggiungere un elemento alla fine di un statearray, è questo il modo corretto di farlo?

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

Temo che la modifica dell'array sul posto pushpossa causare problemi - è sicuro?

L'alternativa di fare una copia dell'array e setStateing sembra inutile.


9
Penso che tu possa usare gli helper di reazione all'immutabilità. vedi questo: facebook.github.io/react/docs/update.html#simple-push
nilgun

setState in elemento di controllare la mia soluzione <br/> <br>. stackoverflow.com/a/59711447/9762736
Rajnikant Lodhi

Risposte:


763

I documenti di React dicono:

Tratta this.state come se fosse immutabile.

L'utente pushcambierà direttamente lo stato e ciò potrebbe potenzialmente portare a un codice soggetto a errori, anche se successivamente lo "ripristini" nuovamente. F.ex, potrebbe portare a che alcuni metodi del ciclo di vita come componentDidUpdatenon si innescano.

L'approccio raccomandato nelle versioni successive di React è l'uso di una funzione di aggiornamento quando si modificano gli stati per prevenire le condizioni di gara:

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

Lo "spreco" di memoria non è un problema rispetto agli errori che potresti riscontrare usando modifiche di stato non standard.

Sintassi alternativa per le versioni precedenti di React

È possibile utilizzare concatper ottenere una sintassi pulita poiché restituisce un nuovo array:

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

In ES6 è possibile utilizzare Spread Operator :

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

8
Puoi fornire un esempio di quando si verificherebbe una condizione di gara?
soundly_typed,

3
@Qiming pushrestituisce la nuova lunghezza dell'array in modo che non funzioni. Inoltre, setStateè asincrono e React può mettere in coda diverse modifiche di stato in un singolo passaggio di rendering.
David Hellsing,

2
@mindeavor dice che hai un animationFrame che cerca i parametri in this.state e un altro metodo che cambia alcuni altri parametri al cambio di stato. Potrebbero esserci dei frame in cui lo stato è cambiato ma non si riflette nel metodo che ascolta la modifica, perché setState è asincrono.
David Hellsing,

1
@ChristopherCamps Questa risposta non incoraggia a chiamare setStatedue volte, mostra due esempi simili di impostazione dell'array di stato senza mutarlo direttamente.
David Hellsing,

2
Un modo semplice per considerare un array di stati come immutabile in questi giorni è: let list = Array.from(this.state.list); list.push('woo'); this.setState({list});modificare ovviamente le preferenze di stile.
basicdays

133

Più semplice, se stai usando ES6.

initialArray = [1, 2, 3];

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

Sarà il nuovo array [1,2,3,4]

per aggiornare il tuo stato in React

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

Ulteriori informazioni sulla distruzione dell'array


@Muzietto puoi elaborare?
StateLess

4
Il nocciolo di questa domanda sta cambiando lo stato di React, non modificando le matrici. Hai visto il mio punto da solo e modificato la tua risposta. Questo rende la tua risposta pertinente. Molto bene.
Marco Faustinelli,

2
Le tue domande non riguardano direttamente la domanda OP
StateLess

1
@ChanceSmith: è necessario anche nella risposta StateLess . Non dipendere dall'aggiornamento dello stato dallo stato stesso. Doc ufficiale: reazionejs.org/docs/…
arcol il

1
@RayCoder registra e controlla il valore di arrayvar, sembra che non sia array.
StateLess

57

Il modo più semplice con ES6:

this.setState(prevState => ({
    array: [...prevState.array, newElement]
}))

Spiacente, nel mio caso voglio inserire un array in un array. tableData = [['test','test']]Dopo aver spinto il mio nuovo array tableData = [['test','test'],['new','new']]. come spingere questo @David e @Ridd
Johncy il

@Johncy Se vuoi [['test','test'],['new','new']]provare:this.setState({ tableData: [...this.state.tableData, ['new', 'new']]
Ridd,

this.setState({ tableData: [...this.state.tableData ,[item.student_name,item.homework_status_name,item.comments===null?'-':item.comments] ] });Inserisce il nuovo array due volte. this.state.tableData.push([item.student_name,item.homework_status_name,item.comments===null?'-':item.comments]);Raggiunge la cosa desiderata che desidero. ma non è il modo corretto penso.
Johncy,

26

React può aggiornare in batch, quindi l'approccio corretto è fornire a setState una funzione che esegua l'aggiornamento.

Per il componente aggiuntivo di aggiornamento React, funzionerà in modo affidabile:

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

o per concat ():

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

Quanto segue mostra cosa https://jsbin.com/mofekakuqi/7/edit?js, come esempio di cosa succede se si sbaglia.

L'invocazione setTimeout () aggiunge correttamente tre elementi perché React non eseguirà il batch batch degli aggiornamenti in un callback setTimeout (consultare https://groups.google.com/d/msg/reactjs/G6pljvpTGX0/0ihYw2zK9dEJ ).

Il buggy onClick aggiungerà solo "Terzo", ma quello fisso aggiungerà F, S e T come previsto.

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'));

C'è davvero un caso in cui succede? Se lo facciamo semplicementethis.setState( update(this.state, {array: {$push: ["First", "Second", "Third"]}}) )
Albizia,

1
@Albizia Penso che dovresti trovare un collega e discuterne con loro. Non si verificano problemi di batch se si sta effettuando una sola chiamata setState. Il punto è mostrare che React batch aggiorna, quindi sì ... c'è davvero un caso, che è quello che puoi trovare nella versione JSBin del codice sopra. Quasi tutte le risposte in questo thread non riescono a risolvere questo problema, quindi ci sarà un sacco di codice là fuori che a volte va storto
NealeU

state.array = state.array.concat([4])questo muta l'oggetto di stato precedente.
Emile Bergeron,

1
@EmileBergeron Grazie per la tua perseveranza. Alla fine ho guardato indietro e ho visto ciò che il mio cervello si stava rifiutando di vedere, e ho controllato i documenti, quindi lo modificherò.
NealeU,

1
Buona! È davvero facile sbagliarsi poiché l'immutabilità in JS non è ovvia (ancora di più quando si tratta dell'API di una libreria).
Emile Bergeron,

17

Come menzionato da @nilgun nel commento, puoi usare gli helper di reazione all'immutabilità . Ho trovato questo super utile.

Dai documenti:

Spinta semplice

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

initialArray è ancora [1, 2, 3].


6
Gli helper di immutabilità di React sono descritti come deprecati nella documentazione. github.com/kolodny/immutability-helper ora dovrebbe essere usato invece.
Periata Breatta,

3

Se si utilizza un componente funzionale, utilizzare questo come di seguito.

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

1

Per l'aggiunta di nuovi elementi nell'array, push()dovrebbe essere la risposta.

Per rimuovere l'elemento e aggiornare lo stato dell'array, il codice seguente funziona per me. splice(index, 1)non può funzionare.

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);

0

Sto cercando di spingere il valore in uno stato di array e impostare un valore come questo e definire l'array di stato e spingere il valore in base alla funzione mappa.

 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})
            })

0

Questo ha funzionato per me per aggiungere un array all'interno di un array

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

0

Ecco un esempio di 2020 di Reactjs Hook che pensavo potesse aiutare gli altri. Lo sto usando per aggiungere nuove righe a una tabella Reactjs. Fammi sapere se potrei migliorare qualcosa.

Aggiunta di un nuovo elemento a un componente di stato funzionale:

Definire i dati sullo stato:

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

Fai in modo che un pulsante attivi una funzione per aggiungere un nuovo 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(', '))
        }

}

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

2
Aggiungi alcune spiegazioni per evitare che questo post sia considerato post di bassa qualità stackoverflow.
Harsha Biyani,

2
@Kobe questa chiamata "operatore di diffusione" in ES6 e aggiungi elementi array2 a array1. grazie per il downvote (:
M. Samiei,

@ M.Samiei Devi modificare il tuo post per evitare il downgrade. È di bassa qualità come solo un frammento di codice. Inoltre, è pericoloso modificare lo stato in questo modo poiché gli aggiornamenti potrebbero essere in batch, quindi il modo preferito è ad es.setState(state => ({arrayvar: [...state.arrayvar, ...newelement]}) );
NealeU

e diffondere l' newelementoggetto all'interno di un array genera TypeErrorcomunque.
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;
}));

-6

Questo codice funziona per me:

fetch('http://localhost:8080')
  .then(response => response.json())
  .then(json => {
    this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
  })

2
tuttavia,
muta

2
E non riesce ad aggiornare correttamente lo stato del componente poiché .pushrestituisce un numero , non un array.
Emile Bergeron,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.