web-dev-qa-db-fra.com

React: Comment mettre à jour state.item [1] sur setState? (avec JSFiddle)

Je crée une application où l'utilisateur peut créer son propre formulaire. Par exemple. spécifiez le nom du champ et les détails des autres colonnes à inclure.

Le composant est disponible en tant que JSFiddle ici .

Mon état initial ressemble à ceci:

var DynamicForm = React.createClass({
  getInitialState: function() {
   var items = {};
   items[1] = { name: 'field 1', populate_at: 'web_start',
                same_as: 'customer_name',
                autocomplete_from: 'customer_name', title: '' };
   items[2] = { name: 'field 2', populate_at: 'web_end',
                same_as: 'user_name', 
                    autocomplete_from: 'user_name', title: '' };

     return { items };
   },

  render: function() {
     var _this = this;
     return (
       <div>
         { Object.keys(this.state.items).map(function (key) {
           var item = _this.state.items[key];
           return (
             <div>
               <PopulateAtCheckboxes this={this}
                 checked={item.populate_at} id={key} 
                   populate_at={data.populate_at} />
            </div>
            );
        }, this)}
        <button onClick={this.newFieldEntry}>Create a new field</button>
        <button onClick={this.saveAndContinue}>Save and Continue</button>
      </div>
    );
  }

Je souhaite mettre à jour l'état lorsque l'utilisateur modifie une valeur, mais j'ai du mal à cibler le bon objet:

var PopulateAtCheckboxes = React.createClass({
  handleChange: function (e) {
     item = this.state.items[1];
     item.name = 'newName';
     items[1] = item;
     this.setState({items: items});
  },
  render: function() {
    var populateAtCheckbox = this.props.populate_at.map(function(value) {
      return (
        <label for={value}>
          <input type="radio" name={'populate_at'+this.props.id} value={value}
            onChange={this.handleChange} checked={this.props.checked == value}
            ref="populate-at"/>
          {value}
        </label>
      );
    }, this);
    return (
      <div className="populate-at-checkboxes">
        {populateAtCheckbox}
      </div>
    );
  }
});

Comment dois-je créer this.setState pour le mettre à jour items[1].name?

157
martins

Vous pouvez utiliser l'auxiliaire update immutability helper }:

this.setState({
  items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})

Ou bien, si vous ne souhaitez pas détecter les modifications apportées à cet élément dans une méthode de cycle de vie shouldComponentUpdate() à l'aide de ===, vous pouvez directement modifier l'état et forcer le composant à effectuer un nouveau rendu. C'est en fait la même chose que la réponse de @limelights car il tire un objet hors de son état et l’édite.

this.state.items[1].name = 'updated field name'
this.forceUpdate()

Ajout post-édition:

Consultez la communication de composant simple leçon de react-training pour un exemple sur la manière de passer une fonction de rappel d'un parent détenteur d'état à un composant enfant devant déclencher un changement d'état .

81
Jonny Buchanan

Fausse route!  

handleChange = (e) => {
    const { items } = this.state;
    items[1].name = e.target.value;

    // update state
    this.setState({
        items,
    });
};

Comme l'ont souligné beaucoup de meilleurs développeurs dans les commentaires: la mutation de l'état est fausse!

Il m'a fallu un peu de temps pour comprendre cela. Au-dessus, ça marche, mais ça enlève le pouvoir de React. Par exemple, componentDidUpdate ne verra pas cela comme une mise à jour car elle est modifiée directement.

Alors la bonne façon serait:

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [this.state.items[1].name]: e.target.value
        },
    }));
};
65
MarvinVK

Comme il y a beaucoup de désinformation dans ce fil, voici comment vous pouvez le faire sans bibliothèque auxiliaire:

handleChange: function (e) {
    // 1. Make a shallow copy of the items
    let items = [...this.state.items];
    // 2. Make a shallow copy of the item you want to mutate
    let item = {...items[1]};
    // 3. Replace the property you're intested in
    item.name = 'newName';
    // 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
    items[1] = item;
    // 5. Set the state to our new copy
    this.setState({items});
},

Vous pouvez combiner les étapes 2 et 3 si vous le souhaitez:

let item = {
    ...items[1],
    name: 'newName'
}

Ou vous pouvez tout faire en une seule ligne:

this.setState(({items}) => ({
    items: [
        ...items.slice(0,1),
        {
            ...items[1],
            name: 'newName',
        },
        ...items.slice(2)
    ]
}));

Note: J'ai fait items un tableau. OP a utilisé un objet. Cependant, les concepts sont les mêmes.


Vous pouvez voir ce qui se passe dans votre terminal/console:

❯ node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied
54
mpen

Tout d’abord, récupérez l’article que vous voulez, changez ce que vous voulez sur cet objet et réglez-le sur l’état . La manière dont vous utilisez l’état en passant seulement un objet dans getInitialState serait beaucoup plus simple si vous utilisiez un objet.

handleChange: function (e) {
   item = this.state.items[1];
   item.name = 'newName';
   items[1] = item;

   this.setState({items: items});
}
24
Henrik Andersson

Ne mute pas l'état en place. Cela peut provoquer des résultats inattendus. J'ai appris ma leçon! Travaillez toujours avec une copie/un clone, Object.assign() en est un bon:

item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

21
Jean Luo

J'ai eu le même problème. Voici une solution simple qui fonctionne! 

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });
14
Jonas

Selon la documentation de React sur setState , utiliser Object.assign comme suggéré par d’autres réponses n’est pas idéal. En raison de la nature du comportement asynchrone de setState, les appels ultérieurs utilisant cette technique peuvent remplacer les appels précédents, entraînant des résultats indésirables.

Au lieu de cela, les documents React recommandent d'utiliser le formulaire de mise à jour de setState qui opère sur l'état précédent. Gardez à l'esprit que lors de la mise à jour d'un tableau ou d'un objet, vous devez renvoyer un nouveau tableau ou objet}, car React nous oblige à préserver l'immuabilité de l'état. En utilisant l'opérateur spread de la syntaxe ES6 pour copier peu profond un tableau, la création ou la mise à jour d'une propriété d'un objet à un index donné du tableau ressemblerait à ceci:

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})
13
eicksl

C'est vraiment simple.

Tout d’abord, extrayez l’objet items entier de l’état, mettez à jour la partie de l’objet items comme vous le souhaitez, puis remettez l’objet items en état avec setState.

handleChange: function (e) {
  items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
  items[1].name = 'newName'; // update the items object as needed
  this.setState({ items }); // Put back in state
}
3
John

Mutation libre:

// given a state
state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]}

// This will work without mutation as it clones the modified item in the map:
this.state.items
   .map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item)

this.setState(newItems)
1
John Wundes

Essayez ceci cela fonctionnera certainement, un autre cas j'ai essayé mais n'ai pas fonctionné

import _ from 'lodash';

this.state.var_name  = _.assign(this.state.var_name, {
   obj_prop: 'changed_value',
});
0
nirav jobanputra

ou si vous avez une liste générée dynamiquement et que vous ne connaissez pas l'index mais avez juste la clé ou l'identifiant:

let ItemsCopy = []
let x = this.state.Items.map((entry) =>{

    if(entry.id == 'theIDYoureLookingFor')
    {
        entry.PropertyToChange = 'NewProperty'
    }

    ItemsCopy.Push(entry)
})


this.setState({Items:ItemsCopy});
0
Jared

Utilisez l'événement sur handleChange pour identifier l'élément qui a été modifié, puis mettez-le à jour. Pour cela, vous devrez peut-être modifier une propriété pour l'identifier et la mettre à jour. 

Voir le violon https://jsfiddle.net/69z2wepo/6164/

0
stringparser

Pourquoi ne pas créer un autre composant (pour un objet devant être inséré dans le tableau) et passer les éléments suivants comme accessoires?

  1. composant index - index sera utilisé pour créer/mettre à jour dans un tableau.
  2. set function - Cette fonction place des données dans le tableau en fonction de l'index du composant.

<SubObjectForm setData={this.setSubObjectData} objectIndex={index}/>

Ici, {index} peut être passé en fonction de la position où ce SubObjectForm est utilisé.

et setSubObjectData peuvent être quelque chose comme ceci.

 setSubObjectData: function(index, data){
      var arrayFromParentObject= <retrieve from props or state>;
      var objectInArray= arrayFromParentObject.array[index];
      arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
 }

Dans SubObjectForm, this.props.setData peut être appelé en cas de modification de données, comme indiqué ci-dessous.

<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>
0

Comme aucune des options ci-dessus n’était idéale pour moi, j’ai fini par utiliser map:

this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })

Je déplacerais le changement de descripteur de fonction et ajouterais un paramètre d'index

handleChange: function (index) {
    var items = this.state.items;
    items[index].name = 'newName';
    this.setState({items: items});
},

au composant de formulaire dynamique et transmettez-le au composant PopulateAtCheckboxes en tant que prop. Lorsque vous passez en boucle sur vos éléments, vous pouvez inclure un compteur supplémentaire (appelé index dans le code ci-dessous) à transmettre au changement de traitement, comme indiqué ci-dessous.

{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
  return (
    <div>
        <PopulateAtCheckboxes this={this}
            checked={item.populate_at} id={key} 
            handleChange={boundHandleChange}
            populate_at={data.populate_at} />
    </div>
);
}, this)}

Enfin, vous pouvez appeler votre auditeur de modification comme indiqué ci-dessous

<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>
0
Kenneth Njendu

La suite du code a été facile sur mon cerveau terne. Supprimer l'objet et le remplacer par celui mis à jour

    var udpateditem = this.state.items.find(function(item) { 
                   return item.name == "field_1" });
    udpateditem.name= "New updated name"                       
    this.setState(prevState => ({                                   
    items:prevState.dl_name_template.filter(function(item) { 
                                    return item.name !== "field_1"}).concat(udpateditem)
    }));
0
Shan

Si vous ne devez modifier qu'une partie de la Array, .__, vous avez un composant de réaction avec l'état défini sur.

state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]}

Il est préférable de mettre à jour le red-one dans la Array comme suit:

const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one');
const newItems = [
   this.state.items.slice(0, itemIndex),
   {name: 'red-one', value: 666},
   this.state.items.slice(itemIndex)
]

this.setState(newItems)
0
att

Essayez avec le code:

this.state.items[1] = 'new value';
var cloneObj = Object.assign({}, this.state.items);

this.setState({items: cloneObj });
0
searching9x