Mise à jour d'un objet avec setState dans React

266

Est-il possible de mettre à jour les propriétés de l'objet avec setState?

Quelque chose comme:

this.state = {
   jasper: { name: 'jasper', age: 28 },
}

J'ai essayé:

this.setState({jasper.name: 'someOtherName'});

et ça:

this.setState({jasper: {name: 'someothername'}})

Le premier entraîne une erreur de syntaxe et le second ne fait rien. Des idées?

JohnSnow
la source
5
le deuxième code aurait fonctionné mais vous auriez perdu la agepropriété à l'intérieur jasper.
giorgim

Réponses:

580

Il existe plusieurs façons de le faire, car la mise à jour de l'état est une opération asynchrone , donc pour mettre à jour l'objet d'état, nous devons utiliser la fonction de mise à jour avec setState.

1- Le plus simple:

Créez d'abord une copie de jasperpuis effectuez les modifications suivantes:

this.setState(prevState => {
  let jasper = Object.assign({}, prevState.jasper);  // creating copy of state variable jasper
  jasper.name = 'someothername';                     // update the name property, assign a new value                 
  return { jasper };                                 // return new object jasper object
})

Au lieu d'utiliser, Object.assignnous pouvons également l'écrire comme ceci:

let jasper = { ...prevState.jasper };

2- Utilisation de l' opérateur d'étalement :

this.setState(prevState => ({
    jasper: {                   // object that we want to update
        ...prevState.jasper,    // keep all other key-value pairs
        name: 'something'       // update the value of specific key
    }
}))

Remarque: Object.assign et Spread Operatorcrée uniquement une copie superficielle , donc si vous avez défini un objet imbriqué ou un tableau d'objets, vous avez besoin d'une approche différente.


Mise à jour de l'objet d'état imbriqué:

Supposons que vous ayez défini l'état comme:

this.state = {
  food: {
    sandwich: {
      capsicum: true,
      crackers: true,
      mayonnaise: true
    },
    pizza: {
      jalapeno: true,
      extraCheese: false
    }
  }
}

Pour mettre à jour l'objet ExtraCheese of pizza:

this.setState(prevState => ({
  food: {
    ...prevState.food,           // copy all other key-value pairs of food object
    pizza: {                     // specific object of food object
      ...prevState.food.pizza,   // copy all pizza key-value pairs
      extraCheese: true          // update value of specific key
    }
  }
}))

Mise à jour du tableau d'objets:

Supposons que vous ayez une application todo et que vous gérez les données sous cette forme:

this.state = {
  todoItems: [
    {
      name: 'Learn React Basics',
      status: 'pending'
    }, {
      name: 'Check Codebase',
      status: 'pending'
    }
  ]
}

Pour mettre à jour le statut de tout objet todo, exécutez une carte sur le tableau et vérifiez la valeur unique de chaque objet, dans le cas contraire condition=true, renvoyez le nouvel objet avec la valeur mise à jour, sinon le même objet.

let key = 2;
this.setState(prevState => ({

  todoItems: prevState.todoItems.map(
    el => el.key === key? { ...el, status: 'done' }: el
  )

}))

Suggestion: si l'objet n'a pas de valeur unique, utilisez un index de tableau.

Mayank Shukla
la source
La façon dont j'essayais fonctionne maintenant ... la deuxième façon: S
JohnSnow
7
@JohnSnow en vous en second lieu, il supprimera les autres propriétés de l' jasperobjet, faites-le, console.log(jasper)vous ne verrez qu'une seule clé name, l'âge ne sera pas là :)
Mayank Shukla
1
@JohnSnow, oui cette façon est appropriée si jasperc'est le cas, n'est object-ce pas le meilleur ou le non, peut-être d'autres meilleures solutions sont possibles :)
Mayank Shukla
6
Bon de mentionner ici que ni Object.assignni propager les propriétés de copie profonde de l'opérateur. De cette façon, vous devez utiliser des solutions de contournement comme lodash, deepCopyetc.
Alex Vasilev
1
Et comment let { jasper } = this.state?
Brian Le
37

C'est le moyen le plus rapide et le plus lisible:

this.setState({...this.state.jasper, name: 'someothername'});

Même s'il this.state.jaspercontient déjà une propriété de nom, le nouveau nom doit name: 'someothername'être utilisé.

mannok
la source
39
Cette solution présente un gros inconvénient: afin d'optimiser les mises à jour d'état, React peut regrouper plusieurs mises à jour. Par conséquent, this.state.jasperne contient pas nécessairement le dernier état. Mieux vaut utiliser la notation avec le prevState.
péché le
33

Utilisez l'opérateur d'étalement et certains ES6 ici

this.setState({
    jasper: {
          ...this.state.jasper,
          name: 'something'
    }
})
Nikhil
la source
Cela met à jour le jaspe mais les autres propriétés sont supprimées de l'état.
iaq
11

J'ai utilisé cette solution.

Si vous avez un état imbriqué comme celui-ci:

this.state = {
  formInputs:{
    friendName:{
      value:'',
      isValid:false,
      errorMsg:''
    },
    friendEmail:{
      value:'',
      isValid:false,
      errorMsg:''
    }
  }
}

vous pouvez déclarer la fonction handleChange qui copie l'état actuel et le réaffecte avec des valeurs modifiées

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

ici le html avec l'écouteur d'événement. Assurez-vous d'utiliser le même nom que celui utilisé dans l'objet d'état (dans ce cas, 'friendName')

<input type="text" onChange={this.handleChange} " name="friendName" />
Alberto Piras
la source
Cela a fonctionné pour moi, sauf que j'ai dû l'utiliser à la place:statusCopy.formInputs[inputName] = inputValue;
MikeyE
10

Je sais qu'il y a beaucoup de réponses ici, mais je suis surpris qu'aucun d'entre eux ne crée une copie du nouvel objet en dehors de setState, puis simplement setState ({newObject}). Propre, concis et fiable. Donc dans ce cas:

const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))

Ou pour une propriété dynamique (très utile pour les formulaires)

const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))

colemerrick
la source
7

essayez ça, ça devrait marcher

this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Burak Kahraman
la source
4
Vous mutez directement l'objet d'état. Pour utiliser cette approche, ajoutez un nouvel objet comme source:Object.assign({}, this.state.jasper, {name:'someOtherName'})
Albizia
3

Le premier cas est en effet une erreur de syntaxe.

Étant donné que je ne peux pas voir le reste de votre composant, il est difficile de voir pourquoi vous imbriquez des objets dans votre état ici. Ce n'est pas une bonne idée d'imbriquer des objets dans l'état du composant. Essayez de définir votre état initial sur:

this.state = {
  name: 'jasper',
  age: 28
}

De cette façon, si vous souhaitez mettre à jour le nom, vous pouvez simplement appeler:

this.setState({
  name: 'Sean'
});

Cela permettra-t-il d'atteindre ce que vous visez?

Pour les magasins de données plus grands et plus complexes, j'utiliserais quelque chose comme Redux. Mais c'est beaucoup plus avancé.

La règle générale avec l'état du composant est de l'utiliser uniquement pour gérer l'état de l'interface utilisateur du composant (par exemple, actif, temporisateurs, etc.)

Découvrez ces références:

mccambridge
la source
1
Je n'ai utilisé cela qu'à titre d'exemple, l'objet doit être imbriqué .. Je devrais probablement utiliser Redux mais j'essaie de comprendre les principes fondamentaux de React ..
JohnSnow
2
Ouais, je resterais loin de Redux pour l'instant. Pendant que vous apprenez les bases, essayez de garder vos données simples. Cela vous aidera à éviter les cas étranges qui vous font trébucher. 👍
mccambridge
2

Autre option: définissez votre variable hors de l' objet Jasper puis appelez simplement une variable.

Opérateur d'étalement: ES6

this.state = {  jasper: { name: 'jasper', age: 28 } } 

let foo = "something that needs to be saved into state" 

this.setState(prevState => ({
    jasper: {
        ...jasper.entity,
        foo
    }
})
Luis Martins
la source
2

Manière simple et dynamique.

Cela fera le travail, mais vous devez définir tous les identifiants sur le parent afin que le parent pointe vers le nom de l'objet, étant id = "jasper" et nomme le nom de la propriété element = input à l'intérieur de l'objet jasper .

handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Alejandro Sanchez Duran
la source
Je suis d'accord avec la première ligne, car c'est la seule chose qui a fonctionné pour mon objet d'objets en état! De plus, cela m'a permis de savoir où j'étais bloqué dans la mise à jour de mes données. De loin la manière la plus simple et la plus dynamique ... Merci !!
Smit
2

Vous pouvez essayer avec ceci : ( Remarque : nom de la balise d'entrée === champ d'objet)

<input name="myField" type="text" 
      value={this.state.myObject.myField} 
     onChange={this.handleChangeInpForm}>
</input>

-----------------------------------------------------------
handleChangeInpForm = (e) => {
   let newObject = this.state.myObject;
   newObject[e.target.name] = e.target.value;
   this.setState({
     myObject: newObject 
   })
}
Xuanduong Ngo
la source
Bienvenue dans Stack Overflow. Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire concernant pourquoi et / ou comment ce code répond à la question améliore sa valeur à long terme. Comment répondre
Elletlar
2

c'est une autre solution utilisant l' utilitaire immer immutabe, très adaptée aux objets profondément imbriqués avec facilité, et vous ne devriez pas vous soucier de la mutation

this.setState(
    produce(draft => {
       draft.jasper.name = 'someothername
    })
)
samehanwar
la source
1

Aussi, en suivant la solution Alberto Piras, si vous ne voulez pas copier tout l'objet "état":

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let jasperCopy = Object.assign({}, this.state.jasper);
    jasperCopy[inputName].name = inputValue;

    this.setState({jasper: jasperCopy});
  }
Marc LaQuay
la source
1

Vous pouvez essayer avec ceci:

this.setState(prevState => {
   prevState = JSON.parse(JSON.stringify(this.state.jasper));
   prevState.name = 'someOtherName';
   return {jasper: prevState}
})

ou pour d'autres biens:

this.setState(prevState => {
   prevState = JSON.parse(JSON.stringify(this.state.jasper));
   prevState.age = 'someOtherAge';
   return {jasper: prevState}
})

Ou vous pouvez utiliser la fonction handleChage:

handleChage(event) {
   const {name, value} = event.target;
    this.setState(prevState => {
       prevState = JSON.parse(JSON.stringify(this.state.jasper));
       prevState[name] = value;
       return {jasper: prevState}
    })
}

et code HTML:

<input 
   type={"text"} 
   name={"name"} 
   value={this.state.jasper.name} 
   onChange={this.handleChange}
/>
<br/>
<input 
   type={"text"} 
   name={"age"} 
   value={this.state.jasper.age} 
   onChange={this.handleChange}
/>
Nemanja Stojanovic
la source
Cela fonctionne sans JSON.stringify .. this.setState (prevState => {prevState = this.state.document; prevState.ValidDate = event.target.value; prevState.ValidDateFormat = dayFormat; return {document: prevState}}); ..Où le document est un objet de type état ..
Oleg Borodko
1

Sans utiliser Async et attendre Utilisez ceci ...

funCall(){    
     this.setState({...this.state.jasper, name: 'someothername'});
}

Si vous utilisez avec Async And Await, utilisez ceci ...

async funCall(){
      await this.setState({...this.state.jasper, name: 'someothername'});
}
Shantanu Sharma
la source
0

Cette configuration a fonctionné pour moi:

let newState = this.state.jasper;
newState.name = 'someOtherName';

this.setState({newState: newState});

console.log(this.state.jasper.name); //someOtherName
LaZza
la source