Modification correcte des tableaux d'état dans ReactJS

417

Je veux ajouter un élément à la fin d'un statetableau, est-ce la bonne façon de le faire?

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

Je crains que la modification de la baie en place avec pushpuisse causer des problèmes - est-ce sûr?

L'alternative de faire une copie du tableau, et setStatecela semble inutile.

fadedbee
la source
9
Je pense que vous pouvez utiliser des aides à l'immuabilité de React. voir ceci: facebook.github.io/react/docs/update.html#simple-push
nilgun
setState dans le tableau d'état vérifier ma solution. <br/> <br> stackoverflow.com/a/59711447/9762736
Rajnikant Lodhi

Réponses:

763

Les documents React disent:

Traitez cet état comme s'il était immuable.

Votre pushmutera directement l'état et cela pourrait potentiellement conduire à un code sujet aux erreurs, même si vous "réinitialisez" à nouveau l'état par la suite. F.ex, cela pourrait conduire à ce que certaines méthodes de cycle de vie comme componentDidUpdatene se déclenchent pas.

L'approche recommandée dans les versions ultérieures de React consiste à utiliser une fonction de mise à jour lors de la modification des états pour éviter les conditions de concurrence:

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

Le «gaspillage» de mémoire n'est pas un problème par rapport aux erreurs que vous pourriez rencontrer en utilisant des modifications d'état non standard.

Syntaxe alternative pour les versions antérieures de React

Vous pouvez utiliser concatpour obtenir une syntaxe propre car elle retourne un nouveau tableau:

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

Dans ES6, vous pouvez utiliser l' opérateur Spread :

this.setState({
  arrayvar: [...this.state.arrayvar, newelement]
})
David Hellsing
la source
8
Pouvez-vous fournir un exemple de cas où une condition de concurrence se produirait?
soundly_typed
3
@Qiming pushrenvoie la nouvelle longueur du tableau afin que cela ne fonctionne pas. En outre, setStateest asynchrone et React peut mettre en file d'attente plusieurs changements d'état en une seule passe de rendu.
David Hellsing
2
@mindeavor indique que vous avez une animationFrame qui recherche des paramètres dans this.state, et une autre méthode qui modifie certains autres paramètres lors d'un changement d'état. Il peut y avoir des trames où l'état a changé mais ne se reflète pas dans la méthode qui écoute la modification, car setState est asynchrone.
David Hellsing
1
@ChristopherCamps Cette réponse n'encourage pas d'appeler setStatedeux fois, elle montre deux exemples similaires de définition d'un tableau d'état sans le muter directement.
David Hellsing
2
Un moyen facile de traiter un tableau d'états comme immuable de nos jours est: let list = Array.from(this.state.list); list.push('woo'); this.setState({list});Modifiez bien sûr vos préférences de style.
jours de base
133

Le plus simple, si vous utilisez ES6.

initialArray = [1, 2, 3];

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

Un nouveau tableau sera [1,2,3,4]

mettre à jour votre état dans React

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

En savoir plus sur la déstructuration des baies

Apatride
la source
@Muzietto pouvez-vous élaborer?
StateLess
4
Le nœud de cette question est de changer l'état de React, pas de modifier les tableaux. Vous avez vu mon point par vous-même et modifié votre réponse. Cela rend votre réponse pertinente. Bien joué.
Marco Faustinelli
2
Vos questions ne concernent pas directement la question OP
StateLess
1
@ChanceSmith: il est également nécessaire dans la réponse StateLess . Ne dépendez pas de la mise à jour de l'état lui-même. Doc officiel: reactjs.org/docs/…
arcol
1
@RayCoder journal et vérifier la valeur de arrayvar, on dirait que ce n'est pas un tableau.
StateLess
57

La manière la plus simple avec ES6:

this.setState(prevState => ({
    array: [...prevState.array, newElement]
}))
Débarrasser
la source
Désolé, dans mon cas, je veux pousser un tableau dans un tableau. tableData = [['test','test']]Après avoir poussé mon nouveau tableau tableData = [['test','test'],['new','new']]. comment pousser ce @David et @Ridd
Johncy
@Johncy Si vous souhaitez [['test','test'],['new','new']]essayer: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] ] });Il insère le nouveau tableau deux fois. this.state.tableData.push([item.student_name,item.homework_status_name,item.comments===null?'-':item.comments]);Il réalise ce que je veux. mais ce n'est pas la bonne façon je pense.
Johncy
26

React peut effectuer des mises à jour par lots et, par conséquent, l'approche correcte consiste à fournir à setState une fonction qui effectue la mise à jour.

Pour l'addon de mise à jour React, les éléments suivants fonctionneront de manière fiable:

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

ou pour concat ():

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

Ce qui suit montre ce que https://jsbin.com/mofekakuqi/7/edit?js,output comme exemple de ce qui se passe si vous vous trompez.

L'invocation setTimeout () ajoute correctement trois éléments car React n'effectuera pas de mises à jour par lots dans un rappel setTimeout (voir https://groups.google.com/d/msg/reactjs/G6pljvpTGX0/0ihYw2zK9dEJ ).

Le buggy onClick n'ajoutera que "Third", mais le fixe, ajoutera F, S et T comme prévu.

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
la source
Y a-t-il vraiment un cas où cela se produit? Si nous faisons simplementthis.setState( update(this.state, {array: {$push: ["First", "Second", "Third"]}}) )
Albizia
1
@Albizia Je pense que vous devriez trouver un collègue et en discuter avec lui. Il n'y a pas de problème de traitement par lots si vous effectuez un seul appel setState. Le but est de montrer que les lots de React sont mis à jour, alors oui ... il y a vraiment un cas, c'est ce que vous pouvez trouver dans la version JSBin du code ci-dessus. Presque toutes les réponses de ce fil ne parviennent pas à résoudre ce problème, il y aura donc beaucoup de code qui va parfois mal
NealeU
state.array = state.array.concat([4])cela mute l'objet d'état précédent.
Emile Bergeron
1
@EmileBergeron Merci pour votre persévérance. J'ai finalement regardé en arrière et vu ce que mon cerveau refusait de voir, et j'ai vérifié les documents, donc je vais éditer.
NealeU
1
Bien! Il est vraiment facile de se tromper car l'immuabilité dans JS n'est pas évidente (encore plus quand il s'agit de l'API d'une bibliothèque).
Emile Bergeron
17

Comme @nilgun l'a mentionné dans le commentaire, vous pouvez utiliser les assistants react immuability . J'ai trouvé que c'était super utile.

De la documentation:

Poussée simple

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

initialArray est toujours [1, 2, 3].

Clarkie
la source
6
Les aides à l'immuabilité de React sont décrites comme obsolètes dans la documentation. github.com/kolodny/immutability-helper devrait maintenant être utilisé à la place.
Periata Breatta
3

Si vous utilisez un composant fonctionnel, veuillez l'utiliser comme ci-dessous.

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
user3444748
la source
1

Pour ajouter un nouvel élément dans le tableau, push()devrait être la réponse.

Pour supprimer l'élément et mettre à jour l'état du tableau, le code ci-dessous fonctionne pour moi. splice(index, 1)ne peut pas fonctionner.

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
la source
0

J'essaie de pousser la valeur dans un état de tableau et de définir une valeur comme celle-ci et de définir le tableau d'état et de pousser la valeur par la fonction de carte.

 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
la source
0

Cela a fonctionné pour moi d'ajouter un tableau dans un tableau

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

Voici un exemple de Reactjs Hook en 2020 qui, selon moi, pourrait aider les autres. Je l'utilise pour ajouter de nouvelles lignes à une table Reactjs. Faites-moi savoir si je peux améliorer quelque chose.

Ajout d'un nouvel élément à un composant d'état fonctionnel:

Définissez les données d'état:

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

Demandez à un bouton de déclencher une fonction pour ajouter un nouvel élément

<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
la source
-1
this.setState({
  arrayvar: [...this.state.arrayvar, ...newelement]
})
M.Samiei
la source
2
Ajoutez quelques explications pour éviter que ce message ne soit considéré comme de mauvaise qualité par stackoverflow.
Harsha Biyani
2
@Kobe cet appel "opérateur de propagation" dans ES6 et ajoutez les éléments array2 à array1. merci pour downvote (:
M.Samiei
@ M.Samiei Vous devez modifier votre message pour éviter les downvotes. C'est de faible qualité comme juste un extrait de code. De plus, il est dangereux de modifier l'état de cette façon car les mises à jour peuvent être groupées, donc la manière préférée est par exemplesetState(state => ({arrayvar: [...state.arrayvar, ...newelement]}) );
NealeU
et la diffusion de l' newelementobjet dans un tableau lance un de TypeErrortoute façon.
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
la source
-6

Ce code fonctionne pour moi:

fetch('http://localhost:8080')
  .then(response => response.json())
  .then(json => {
    this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
  })
Hamid Hosseinpour
la source
2
encore, vous
mutez l'
2
Et ne parvient pas à mettre à jour correctement l'état du composant car .pushrenvoie un nombre , pas un tableau.
Emile Bergeron