wake-up-neo.net

Reaktion: Wie aktualisiere ich state.item [1] in setState? (mit JSFiddle)

Ich erstelle eine App, in der der Benutzer sein eigenes Formular erstellen kann. Z.B. Geben Sie den Namen des Feldes und Details an, welche anderen Spalten enthalten sein sollen.

Die Komponente ist als JSFiddle hier verfügbar.

Mein Ausgangszustand sieht folgendermaßen aus:

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

Ich möchte den Status aktualisieren, wenn der Benutzer einen der Werte ändert, es fällt mir jedoch schwer, auf das richtige Objekt zu zielen:

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

Wie soll ich this.setState herstellen, damit items[1].name aktualisiert wird?

157
martins

Sie können den update-Anpassungshelfer für dieses verwenden:

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

Wenn Sie nicht daran interessiert sind, Änderungen an diesem Element in einer shouldComponentUpdate()-Lebenszyklusmethode mithilfe von === zu erkennen, können Sie den Status direkt bearbeiten und die erneute Wiedergabe der Komponente erzwingen. Dies ist praktisch die Antwort von @limelights. da es ein Objekt aus dem Status zieht und bearbeitet.

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

Nachbearbeitungszusatz:

Schauen Sie sich die Simple Component Communication Lektion von React-Training an, um zu erfahren, wie Sie eine Rückruffunktion von einem übergeordneten Status an eine untergeordnete Komponente übergeben können, die eine Statusänderung auslösen muss.

81
Jonny Buchanan

Falscher Weg!  

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

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

Wie von vielen besseren Entwicklern in den Kommentaren darauf hingewiesen wurde: Mutation des Staates ist falsch!

Ich habe eine Weile gebraucht, um das herauszufinden. Oben funktioniert, aber es nimmt die Kraft von React. Zum Beispiel wird componentDidUpdate dies nicht als Update sehen, da es direkt geändert wird.

Also der richtige Weg wäre:

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

Da es in diesem Thread viele Fehlinformationen gibt, können Sie dies ohne Helper-Bibliotheken tun:

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

Sie können die Schritte 2 und 3 kombinieren, wenn Sie möchten:

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

Oder Sie können das Ganze in einer Zeile machen:

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

Hinweis: Ich habe items ein Array erstellt. OP hat ein Objekt verwendet. Die Konzepte sind jedoch die gleichen.


Sie können in Ihrem Terminal/Ihrer Konsole sehen, was los ist:

❯ 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

Um tief verschachtelte Objekte/Variablen im React-Status zu ändern, werden normalerweise drei Methoden verwendet: Vanilla JS Object.assign, nveränderlichkeitshilfe und cloneDeep from Lodash . Es gibt auch viele andere weniger beliebte Drittanbieter-Bibliotheken, um dies zu erreichen. In dieser Antwort werde ich jedoch nur auf diese drei Optionen eingehen. Es gibt auch einige Methoden, die nichts anderes als Vanilla-JavaScript verwenden, wie z. B. das Verteilen von Arrays (siehe die Antwort von @ mpen), aber sie sind nicht sehr intuitiv, einfach zu verwenden und in der Lage, alle Zustandsmanipulationssituationen zu handhaben.

Wie bereits unzählige Male in Kommentaren zu Antworten erwähnt wurde, deren Autoren eine direkte Zustandsänderung vorschlagen, mach das einfach nicht. Dies ist ein allgegenwärtiges React Anti-Muster, das unweigerlich zu unerwünschten Konsequenzen führen wird. Lerne den richtigen Weg.

Vergleichen wir drei weit verbreitete Methoden.

Anmerkung: Diese Beispiele verwenden die ältere Methode zum Aktualisieren des Status mit Klassenkomponenten und Lebenszyklen. Die neuere Hooks-API hat eine andere Syntax, aber die Idee ist immer noch dieselbe. Das heißt, dass alle diese Beispiele noch gültig sind.

Angesichts dieser Zustandsstruktur:

state = {
    foo: {
        bar: 'initial value'
    }
}

1. Vanilla JavaScript's Object.assign

(...essential imports)
class App extends Component {

    state = {
        foo: {
            bar: 'initial value'
        }
    }

    componentDidMount() {

        console.log(this.state.foo.bar) // initial value

        const foo = Object.assign({}, this.state.foo, { bar: 'further value' })

        console.log(this.state.foo.bar) // initial value

        this.setState({ foo }, () => {
            console.log(this.state.foo.bar) // further value
        })
    }
    (...rest of code)

Denken Sie daran, dass Object.assignkein tiefes Klonen durchführt, da es nur Eigenschaftswerte kopiert und deshalb das ist, was es tut genannt ein flaches Kopieren (siehe Kommentare).

Damit dies funktioniert, sollten wir nur die obersten Elemente dieses Objekts bearbeiten items (state.foo). Und ihre Werte (state.foo.bar) Sollten primitiv (Zeichenketten, Zahlen, Boolesche Werte) sein.

In diesem Beispiel erstellen wir eine neue Konstante (const foo...) Mit Object.assign, Die ein leeres Objekt ({}) Erstellt und das Objekt state.foo Kopiert ({ bar: 'initial value' }) Hinein und kopiert dann ein anderes Objekt { bar: 'further value' } Darüber. Am Ende enthält die neu erstellte foo-Konstante den Wert { bar: 'further value' }, Da die bar -Eigenschaft überschrieben wurde. Dieses foo ist ein brandneues Objekt, das nicht mit dem Statusobjekt verknüpft ist. Daher kann es nach Bedarf mutiert werden, und der Status ändert sich nicht.

Der letzte Teil besteht darin, setState() setter zu verwenden, um das ursprüngliche state.foo Im Status durch ein neu erstelltes foo Objekt zu ersetzen.

Stellen Sie sich vor, wir haben einen tieferen Zustand wie state = { foo: { bar: { baz: 'initial value' } } }. Wir könnten versuchen, ein neues foo -Objekt zu erstellen und es mit dem foo -Inhalt aus dem Status zu füllen, aber Object.assign Kann den baz -Wert nicht kopieren zu diesem neu erstellten foo Objekt, da baz zu tief verschachtelt ist. Sie können noch bar kopieren, wie im obigen Beispiel, aber da es sich jetzt um ein Objekt und nicht um ein Grundelement handelt, wird stattdessen die Referenz aus state.foo.bar Kopiert, was bedeutet, dass wir am Ende damit enden werden lokales foo Objekt, das direkt an den Zustand gebunden ist. Das bedeutet, dass in diesem Fall alle Mutationen des lokal erstellten foo das state.foo - Objekt beeinflussen, da sie tatsächlich auf dasselbe verweisen.

Object.assign Funktioniert daher nur, wenn Sie eine relativ einfache einstufige Struktur mit tiefen Zuständen haben und die innersten Elemente Werte des primitiven Typs enthalten.

Wenn Sie tiefere Objekte (2. Stufe oder höher) haben, die Sie aktualisieren sollten, verwenden Sie nicht Object.assign. Sie riskieren den Mutationszustand direkt.

2. Lodashs cloneDeep

(...essential imports)
import cloneDeep from 'lodash.clonedeep'

class App extends Component {

    state = {
        foo: {
            bar: 'initial value'
        }
    }

    componentDidMount() {

        console.log(this.state.foo.bar) // initial value

        const foo = cloneDeep(this.state.foo)

        foo.bar = 'further value'  

        console.log(this.state.foo.bar) // initial value

        this.setState({ foo }, () => {
            console.log(this.state.foo.bar) // further value
        })
    }
    (...rest of code)

Lodash's cloneDeep ist viel einfacher zu benutzen. Es führt deep cloning aus, daher ist es eine robuste Option, wenn Sie einen ziemlich komplexen Zustand mit mehrstufigen Objekten oder Arrays haben. Nur cloneDeep() die Staatseigenschaft der obersten Ebene, mutieren Sie den geklonten Teil, wie Sie möchten, und setState() setzen Sie ihn auf den Staat zurück.

3. nveränderlichkeitshilfe

(...essential imports)
import update from 'immutability-helper'

class App extends Component {

    state = {
        foo: {
            bar: 'initial value'
        }
    };

    componentDidMount() {

        console.log(this.state.foo.bar) // initial value     

        const foo = update(this.state.foo, { bar: { $set: 'further value' } })  

        console.log(this.state.foo.bar) // initial value

        this.setState({ foo }, () => {
            console.log(this.state.foo.bar) // further value
        });
    }    
    (...rest of code)

immutability-helper Bringt es auf eine völlig neue Ebene, und das Tolle daran ist, dass es nicht nur $set - Werte für Statuselemente geben kann, sondern auch $Push, $splice, $merge (Usw.). Hier ist ein Liste der Befehle verfügbar.

Randnotizen

Denken Sie auch hier daran, dass this.setState() nur die first-level properties des state-Objekts (in diesem Fall die foo -Eigenschaft) ändert, nicht die tief verschachtelte ( foo.bar). Wenn es sich anders verhalten würde, gäbe es diese Frage nicht.

Übrigens ist this.setState({ foo }) nur eine Abkürzung für this.setState({ foo: foo }). Und () => { console.log(this.state.foo.bar) } Nach dem { foo } Ist ein Rückruf, der sofort ausgeführt wird, nachdem setState den Status gesetzt hat. Praktisch, wenn Sie einige Dinge tun müssen, nachdem es seine Aufgabe erfüllt hat (in unserem Fall, um den Status unmittelbar nach dem Festlegen anzuzeigen).

Welches ist richtig für Ihr Projekt?

Wenn Sie externe Abhängigkeiten nicht verwenden möchten oder können und eine einfache Statusstruktur haben, bleiben Sie bei Object.assign.

Wenn Sie einen riesigen und/oder komplexen Zustand manipulieren, ist Lodashs cloneDeep eine kluge Wahl.

Wenn Sie erweiterte Funktionen benötigen, dh wenn Ihre Statusstruktur komplex ist und Sie alle Arten von Operationen ausführen müssen, versuchen Sie es mit immutability-helper, Einem sehr fortschrittlichen Tool, das verwendet werden kann staatliche Manipulation.

44

Besorgen Sie sich zunächst das gewünschte Objekt, ändern Sie das gewünschte Objekt und setzen Sie es wieder in den Zustand. Die Art und Weise, wie Sie den Status verwenden, indem Sie nur ein Objekt in getInitialState übergeben, wäre viel einfacher, wenn Sie einen Schlüssel verwenden Objekt.

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

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

Verändern Sie den Zustand nicht an Ort und Stelle. Dies kann zu unerwarteten Ergebnissen führen. Ich habe meine Lektion gelernt! Immer mit einer Kopie/einem Klon arbeiten, Object.assign() ist eine gute:

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

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

21
Jean Luo

Ich hatte das gleiche Problem. Hier ist eine einfache Lösung, die funktioniert! 

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

Laut der React-Dokumentation zu setState ist die Verwendung von Object.assign, wie von anderen Antworten vorgeschlagen, nicht ideal. Aufgrund der Art des asynchronen Verhaltens von setState können nachfolgende Aufrufe, die diese Technik verwenden, frühere Aufrufe überschreiben, was zu unerwünschten Ergebnissen führt.

Stattdessen empfehlen die React-Dokumente, die Aktualisierungsform von setState zu verwenden, die im vorherigen Zustand ausgeführt wird. Beachten Sie, dass beim Aktualisieren eines Arrays oder Objekts Sie ein neues Array oder Objekt zurückgeben müssen, da React die Unveränderlichkeit des Zustands erfordert. Wenn Sie den Spread-Operator der ES6-Syntax verwenden, um ein Array flach zu kopieren, eine Eigenschaft eines Objekts an einem bestimmten Index des Arrays zu erstellen oder zu aktualisieren, würde dies folgendermaßen aussehen:

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

Es ist wirklich einfach.

Ziehen Sie zunächst das gesamte Element aus dem Status heraus, aktualisieren Sie den Teil des Elementes wie gewünscht und setzen Sie das gesamte Element über setState in den Status zurück.

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 frei:

// 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

Versuchen Sie dies, es wird definitiv funktionieren, ein anderer Fall, den ich ausprobiert habe, aber nicht funktioniert hat

import _ from 'lodash';

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

oder wenn Sie eine dynamisch generierte Liste haben und den Index nicht kennen, sondern nur den Schlüssel oder die ID:

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

Verwenden Sie das Ereignis auf handleChange, um das geänderte Element herauszufinden, und aktualisieren Sie es. Dafür müssen Sie möglicherweise einige Eigenschaften ändern, um sie zu identifizieren und zu aktualisieren. 

Siehe Geige https://jsfiddle.net/69z2wepo/6164/

0
stringparser

Wie wäre es mit dem Erstellen einer anderen Komponente (für ein Objekt, das in das Array aufgenommen werden muss) und Folgendes als Requisiten übergeben?

  1. komponentenindex - Index wird zum Erstellen/Aktualisieren in Array verwendet.
  2. set function - Diese Funktion fügt Daten basierend auf dem Komponentenindex in das Array ein.

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

Hier kann {index} basierend auf der Position übergeben werden, an der dieses SubObjectForm verwendet wird.

und setSubObjectData können so etwas sein.

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

In SubObjectForm kann this.props.setData bei einer Datenänderung wie unten angegeben aufgerufen werden.

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

Da keine der oben genannten Optionen für mich ideal war, habe ich am Ende die Karte verwendet:

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

Ich würde das Funktionshandle ändern und einen Indexparameter hinzufügen

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

an die dynamische Formularkomponente und übergeben Sie sie als Requisite an die PopulateAtCheckboxes-Komponente. Während Sie Ihre Artikel durchlaufen, können Sie einen zusätzlichen Zähler (im folgenden Code als Index bezeichnet) hinzufügen, der wie unten gezeigt an die Handle-Änderung übergeben wird

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

Zum Schluss können Sie Ihren Änderungslistener wie unten gezeigt aufrufen

<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

Das Befolgen des Codes hat mein langweiliges Gehirn geschont. Entfernen Sie das Objekt und ersetzen Sie es durch das aktualisierte

    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

Wenn Sie nur einen Teil der Array, Ändern müssen, haben Sie eine Reaktionskomponente, deren Status auf gesetzt ist.

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

Es ist am besten, den red-one in der Array wie folgt zu aktualisieren:

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

Versuchen Sie es mit Code:

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

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