Reaktion: Wie aktualisiere ich state.item [1] im Status mit setState?


259

Ich erstelle eine App, in der der Benutzer sein eigenes Formular entwerfen kann. Geben Sie beispielsweise den Namen des Felds und die Details der anderen Spalten an, die enthalten sein sollen.

Die Komponente ist als JSFiddle verfügbar hier .

Mein Ausgangszustand sieht so 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, aber es fällt mir schwer, das richtige Objekt zu finden:

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 basteln this.setState, damit es aktualisiert wird items[1].name?



1
Was ist Ihre ausgewählte Antwort auf diese Frage?
Braian Mellor

Antworten:


126

Sie können den updateUnveränderlichkeitshelfer dafür verwenden :

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

Wenn es Ihnen nicht wichtig ist, Änderungen an diesem Element in einer shouldComponentUpdate()Lebenszyklusmethode mithilfe von zu erkennen ===, können Sie den Status direkt bearbeiten und das erneute Rendern der Komponente erzwingen. Dies entspricht praktisch der Antwort von @limelights Ziehen eines Objekts aus dem Status und Bearbeiten.

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

Nachbearbeitungszusatz:

In der Lektion zur Kommunikation einfacher Komponenten aus dem Reaktionstraining finden Sie ein Beispiel für die Übergabe einer Rückruffunktion von einem übergeordneten Element an eine untergeordnete Komponente, die eine Statusänderung auslösen muss.


125

Da dieser Thread viele Fehlinformationen enthält, können Sie dies wie folgt ohne Hilfsbibliotheken 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 erledigen:

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

Hinweis: Ich habe itemsein Array erstellt. OP hat ein Objekt verwendet. Die Konzepte sind jedoch dieselben.


Sie können sehen, was in Ihrem Terminal / Ihrer Konsole 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

4
@TranslucentCloud Oh ja, die Hilfsmethoden sind auf jeden Fall nett, aber ich denke, jeder sollte wissen, was unter der Haube los ist :-)
mpen

Warum brauchen wir Schritt 2 und 3? Warum können wir den Klon nach dem ersten Schritt nicht direkt mutieren?
Evmorov

1
@ Evmorov Weil es kein tiefer Klon ist. In Schritt 1 wird nur das Array geklont, nicht die darin enthaltenen Objekte. Mit anderen Worten, jedes der Objekte im neuen Array "zeigt" immer noch auf die vorhandenen Objekte im Speicher. Wenn Sie eines mutieren, wird das andere mutiert (sie sind ein und dasselbe). Siehe auch das items[0] === clone[0]Bit in meinem Terminal-Beispiel unten. Dreifach =prüft, ob sich die Objekte auf dasselbe beziehen.
Mpen

Wird komplizierter, wenn Sie den Index des Elements im Array nicht kennen.
Ian Warburton

@ IanWarburton Nicht wirklich. items.findIndex()sollte kurze Arbeit daraus machen.
Mpen

92

Falscher Weg!

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

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

Wie viele bessere Entwickler in den Kommentaren betonten : Mutation des Staates ist falsch!

Ich habe eine Weile gebraucht, um das herauszufinden. Oben funktioniert, aber es nimmt die Kraft von React weg. componentDidUpdateDies wird beispielsweise nicht als Update angezeigt, da es direkt geändert wird.

Also der richtige Weg wäre:

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

25
ES6 nur weil Sie "const" verwenden?
Nkkollaw

49
items[1].role = e.target.valueRuft der mutierende Zustand nicht direkt auf?
Antony

28
Wenn Sie den Staat mutieren, ist dies völlig gegen die Idee, den Staat unveränderlich zu halten, wie es die Reaktion vermuten lässt. Dies kann Ihnen bei einer großen Anwendung große Schmerzen bereiten.
Ncubica

8
@MarvinVK, Ihre Antwort lautet "Best Practice wäre also:", gefolgt von der Verwendung von "this.forceUpdate ();" Dies wird nicht empfohlen, da es von setState () überschrieben werden könnte, siehe facebook.github.io/react/docs/react-component.html#state . Am besten ändern Sie dies, damit es für zukünftige Leser nicht verwirrend ist.
James Z.

8
Ich wollte nur darauf hinweisen, dass viele der Kommentare aufgrund von Änderungen jetzt irrelevant sind und der richtige Weg tatsächlich der richtige Weg ist.
Heez

50

Um tief verschachtelte Objekte / Variablen im Status von React zu ändern, werden normalerweise drei Methoden verwendet: Vanilla JavaScript Object.assign, Unveränderlichkeitshelfer und cloneDeepvon Lodash .

Es gibt auch viele andere weniger beliebte Bibliotheken von Drittanbietern, um dies zu erreichen, aber in dieser Antwort werde ich nur diese drei Optionen behandeln. Es gibt auch einige zusätzliche Vanilla-JavaScript-Methoden, wie z. B. das Verbreiten von Arrays (siehe z. B. die Antwort von @ mpen), die jedoch nicht sehr intuitiv, einfach zu verwenden und in der Lage sind, alle Zustandsmanipulationssituationen zu bewältigen.

Wie unzählige Male in den oben abgestimmten Kommentaren zu den Antworten erwähnt wurde, deren Autoren eine direkte Mutation des Staates vorschlagen: Tu das einfach nicht . Dies ist ein allgegenwärtiges Reaktionsmuster, das unweigerlich zu unerwünschten Konsequenzen führt. Lerne den richtigen Weg.

Vergleichen wir drei weit verbreitete Methoden.

Angesichts dieser Zustandsobjektstruktur:

state = {
    outer: {
        inner: 'initial value'
    }
}

Sie können die folgenden Methoden verwenden, um das Innerste zu aktualisieren inner den Wert Felds ohne den Rest des Status zu beeinflussen.

1. Object.assign von Vanilla JavaScript

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the shallow copying:', outer.inner) // initial value
    const newOuter = Object.assign({}, outer, { inner: 'updated value' })
    console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

<main id="react"></main>

Beachten Sie, dass Object.assign kein tiefes Klonen ausführt , da nur Eigenschaftswerte kopiert werden . Daher wird das, was es tut, als flaches Kopieren bezeichnet (siehe Kommentare).

Damit dies funktioniert, sollten wir nur die Eigenschaften primitiver Typen ( outer.inner) bearbeiten, dh Zeichenfolgen, Zahlen, Boolesche Werte.

In diesem Beispiel erstellen wir eine neue Konstante ( const newOuter...), Object.assigndie ein leeres Objekt ( {}) erstellt, outerobject ( { inner: 'initial value' }) hinein kopiert und dann ein anderes Objekt { inner: 'updated value' } darüber kopiert .

Auf diese Weise enthält die neu erstellte newOuterKonstante am Ende einen Wert von, { inner: 'updated value' }da die innerEigenschaft überschrieben wurde. Dies newOuterist ein brandneues Objekt, das nicht mit dem Objekt im Status verknüpft ist, sodass es nach Bedarf mutiert werden kann. Der Status bleibt unverändert und wird erst geändert, wenn der Befehl zum Aktualisieren ausgeführt wird.

Der letzte Teil besteht darin, den setOuter()Setter zu verwenden, um das Original outerim Status durch ein neu erstelltes newOuterObjekt zu ersetzen (nur der Wert ändert sich, der Eigenschaftsname outernicht).

Stellen Sie sich nun vor, wir haben einen tieferen Zustand wie state = { outer: { inner: { innerMost: 'initial value' } } }. Wir könnten versuchen, das newOuterObjekt zu erstellen und es mit dem outerInhalt des Status Object.assignzu füllen innerMost, können jedoch den Wert nicht in dieses neu erstellte newOuterObjekt kopieren , da innerMostes zu tief verschachtelt ist.

Sie können weiterhin kopieren inner, wie im obigen Beispiel, aber da es sich nun um ein Objekt und nicht um ein Grundelement handelt, wird die Referenz von stattdessen auf das newOuter.innerkopiert outer.inner, was bedeutet, dass das lokale newOuterObjekt direkt mit dem Objekt im Status verknüpft ist .

Das bedeutet, dass in diesem Fall Mutationen des lokal erstellten Objekts newOuter.innerdas outer.innerObjekt (im Status) direkt beeinflussen , da sie tatsächlich dasselbe geworden sind (im Speicher des Computers).

Object.assign Daher funktioniert es nur, wenn Sie eine relativ einfache einstufige Tiefenzustandsstruktur mit innersten Elementen haben, die Werte vom primitiven Typ enthalten.

Wenn Sie tiefere Objekte (2. Ebene oder mehr) haben, die Sie aktualisieren sollten, verwenden Sie diese nicht Object.assign. Sie riskieren eine direkte Mutation des Zustands.

2. Lodashs CloneDeep

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the deep cloning:', outer.inner) // initial value
    const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
    newOuter.inner = 'updated value'
    console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

<main id="react"></main>

Lodashs cloneDeep ist viel einfacher zu bedienen. Es führt ein tiefes Klonen durch , daher ist es eine robuste Option, wenn Sie einen ziemlich komplexen Status mit mehrstufigen Objekten oder Arrays haben. Nur cloneDeep()die State-Eigenschaft der obersten Ebene, mutieren Sie den geklonten Teil nach Belieben und kehren Sie setOuter()zum State zurück.

3. Unveränderlichkeitshelfer

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })
  
  React.useEffect(() => {
    const update = immutabilityHelper
    console.log('Before the deep cloning and updating:', outer.inner) // initial value
    const newOuter = update(outer, { inner: { $set: 'updated value' } })
    console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper@3.0.0"></script>

<main id="react"></main>

immutability-helperauf eine ganz neue Ebene dauert es, und die kühle daran ist , dass es nicht nur in $setdem Zustand Artikel Werte, sondern auch $push, $splice, $merge(etc.) sie. Hier ist eine Liste der verfügbaren Befehle .

Randnotizen

Beachten Sie auch hier, dass setOuternur die Eigenschaften der ersten Ebene des Statusobjekts ( outerin diesen Beispielen) geändert werden, nicht die tief verschachtelten ( outer.inner). Wenn es sich anders verhalten würde, würde diese Frage nicht existieren.

Welches ist das richtige für Ihr Projekt?

Wenn Sie keine externen Abhängigkeiten 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, cloneDeepist Lodash 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 immutability-helper, es ist ein sehr erweitertes Tool, das für die Statusmanipulation verwendet werden kann.

... oder müssen Sie das wirklich überhaupt tun?

Wenn Sie komplexe Daten im Status von React halten, ist dies möglicherweise ein guter Zeitpunkt, um über andere Möglichkeiten nachzudenken, wie Sie damit umgehen können. Das Festlegen komplexer Statusobjekte in React-Komponenten ist keine einfache Operation, und ich empfehle dringend, über verschiedene Ansätze nachzudenken.

Höchstwahrscheinlich sollten Sie Ihre komplexen Daten nicht in einem Redux-Speicher aufbewahren, sie dort mithilfe von Reduzierungen und / oder Sagen festlegen und mithilfe von Selektoren darauf zugreifen.


Object.assignführt keine tiefe Kopie durch. Sagen Sie, wenn a = {c: {d: 1}}und b = Object.assign({}, a), dann führen Sie aus b.c.d = 4, dann a.c.dist mutiert.
Awol

Sie haben Recht, der Wert 1des innersten Objekts ( a.c.d) wird mutiert. Wenn Sie jedoch den Nachfolger der ersten Ebene bwie folgt neu zuweisen : b.c = {f: 1}wird der entsprechende Teil von anicht mutiert (es bleibt {d: 1}). Trotzdem ein guter Fang, ich werde die Antwort sofort aktualisieren.
Neurotransmitter

Was Sie definiert haben, ist eigentlich ein shallow copyund kein deep copy. Es ist leicht zu verwechseln, was shallow copybedeutet. In shallow copy, a !== baber für jeden Schlüssel vom aa[key] === b[key]
Awol

Ja, ausdrücklich erwähnte Oberflächlichkeit von Object.assignin der Antwort.
Neurotransmitter

JSON.parse(JSON.stringify(object))ist auch eine Variante für Deep Clone. Die Leistung ist jedoch schlechter als lodash cloneDeep. messethat.net/Benchmarks/Show/2751/0/…
tylik

35

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

11
@TranslucentCloud - dies ist mit Sicherheit keine direkte Mutation. Das ursprüngliche Array wurde geklont, geändert und anschließend der Status mithilfe des geklonten Arrays erneut festgelegt.
vsync

@vsync Ja, jetzt, nachdem Sie die ursprüngliche Antwort bearbeitet haben, ist dies überhaupt keine Mutation.
Neurotransmitter

2
@TranslucentCloud - Vorher hat meine Bearbeitung auch nichts damit zu tun, vielen Dank für die Einstellung. @ Jonas hier machte nur einen einfachen Fehler in seiner Antwort, indem er {geschweifte Klammern anstelle von Klammern verwendete, die ich behoben hatte
vsync

31

Laut der React-Dokumentation zu setState ist die Verwendung, Object.assignwie in anderen Antworten vorgeschlagen, hier nicht ideal. Aufgrund der Art des setStateasynchronen Verhaltens können nachfolgende Aufrufe mit dieser Technik frühere Aufrufe überschreiben und unerwünschte Ergebnisse verursachen.

In den React-Dokumenten wird stattdessen empfohlen, das Updater-Formular zu verwenden, setStatedas im vorherigen Status ausgeführt wird. Beachten Sie, dass Sie beim Aktualisieren eines Arrays oder Objekts ein neues Array oder Objekt zurückgeben müssen, da React erfordert, dass der Status unveränderlich bleibt. Wenn Sie den Spread-Operator der ES6-Syntax zum flachen Kopieren eines Arrays verwenden und eine Eigenschaft eines Objekts an einem bestimmten Index des Arrays erstellen oder aktualisieren, sieht dies folgendermaßen aus:

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

2
Dies ist die richtige Antwort, wenn Sie ES6 verwenden. Es ist inline, was @Jonas geantwortet hat. Aufgrund der Erklärung fällt es jedoch auf.
Sourabh

Ja, dieser Code funktioniert einwandfrei und ist sehr einfach.
Shoeb Mirza

29

Holen Sie sich zuerst das gewünschte Element, ändern Sie die gewünschten Elemente für dieses Objekt und setzen Sie es wieder auf den Status. Die Art und Weise, wie Sie den Status verwenden, indem Sie nur ein Objekt übergeben, getInitialStatewäre viel einfacher, wenn Sie ein Schlüsselobjekt verwenden würden.

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

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

3
Nein, es gibt nach Uncaught TypeError: Cannot read property 'items' of null.
Martins

Nein, wird es nicht. Ihr Fehler ist höchstwahrscheinlich auf Ihre Vorgehensweise zurückzuführen getInitialState.
Henrik Andersson

10
@HenrikAndersson In Ihrem Beispiel scheint etwas nicht zu stimmen. itemsist nirgendwo definiert.
Edward D'Souza

1
@ EdwardD'Souza Du bist absolut richtig! Meine Antwort ist zu zeigen, wie es definiert und verwendet werden sollte. Die Art und Weise, wie der Code des Fragestellers eingerichtet wird, funktioniert nicht für das, was er / sie möchte, weshalb die Notwendigkeit eines verschlüsselten Objekts erforderlich ist.
Henrik Andersson

7
Dies ist ein typisches Reaktionsmuster. Sie weisen itemder eigenen this.state.items[1]Variablen des Status einen Verweis zu . Dann ändern Sie item( item.name = 'newName') und mutieren so den Status direkt, was dringend empfohlen wird. In Ihrem Beispiel müssen Sie nicht einmal aufrufen this.setState({items: items}), da der Status bereits direkt mutiert ist.
Neurotransmitter

22

Mutieren Sie den Staat nicht. Dies kann zu unerwarteten Ergebnissen führen. Ich habe meine Lektion gelernt! Arbeiten Sie immer mit einer Kopie / einem Klon, 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/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


8
Was ist itemsin deinem Beispiel? Meinten Sie this.state.itemsoder etwas anderes?
Buh Buh

Ich habe das getestet, aber ihm fehlt eine Zeile. Oben items[1] = item;sollte eine Zeile stehen items = this.state.items;. Passen Sie auf, dass mein Javascript rostig ist und ich lerne, auf mein
Heimprojekt zu

4

Es ist wirklich einfach.

Ziehen Sie zuerst das gesamte Elementobjekt aus dem Status, aktualisieren Sie den Teil des Elementobjekts wie gewünscht und versetzen Sie das gesamte Elementobjekt über setState wieder in den Status.

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
}

"Object.assign funktioniert, wenn Sie eine relativ einfache einstufige Tiefenzustandsstruktur mit innersten Elementen haben, die Werte vom primitiven Typ enthalten."
Neurotransmitter

3

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

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

2

Mutationsfrei:

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

2
Ich kann nicht sehen, wo newItemseingestellt ist.
Neurotransmitter

Ist eine Karte plus Vergleich im Array nicht für die Leistung schrecklich?
Natassia Tavares

@ NatassiaTavares .. was? Vielleicht sind Sie verwirrt fo ofoder die forEachKarte ist die schnellste.
Deano

1

Fand dies überraschend schwierig und keine der ES6-Verbreitungsmagien schien wie erwartet zu funktionieren. Verwenden Sie eine solche Struktur, um gerenderte Elementeigenschaften für Layoutzwecke abzurufen.

In diesem vereinfachten Beispiel wurde festgestellt, dass die updateMethode von immutability-helperdie einfachste ist:

constructor(props) {
    super(props)
    this.state = { values: [] }
    this.updateContainerState = this.updateContainerState.bind(this)
  }

updateContainerState(index, value) {
    this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
  }

wie von https://github.com/kolodny/immutability-helper#computed-property-names angepasst

des zu aktualisierenden Array-Mitglieds ist ein stärker verschachteltes komplexes Objekt. Verwenden Sie die entsprechende Deep-Copy-Methode die auf der Komplexität basiert.

Es gibt sicherlich bessere Möglichkeiten, mit Layoutparametern umzugehen, aber hier geht es darum, wie man mit Arrays umgeht. Die relevanten Werte für jedes untergeordnete Element könnten auch außerhalb von ihnen berechnet werden. Ich fand es jedoch bequemer, containerState weiterzugeben, damit die untergeordneten Elemente nach Belieben Eigenschaften abrufen und das übergeordnete Statusarray an ihrem angegebenen Index aktualisieren können.

import React from 'react'
import update from 'immutability-helper'
import { ContainerElement } from './container.component.style.js'
import ChildComponent from './child-component'
export default class ContainerComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { values: [] }
    this.updateContainerState = this.updateContainerState.bind(this)
  }

  updateContainerState(index, value) {
    this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
  }

  // ...

  render() {
    let index = 0
    return (
      <ContainerElement>
      <ChildComponent
        index={index++}
        containerState={this.state}
        updateContainerState={this.updateContainerState}
      />
      <ChildComponent
        index={index++}
        containerState={this.state}
        updateContainerState={this.updateContainerState}
      />
      </ContainerElement>
    )
  }
}

1

Verwenden Sie die Array-Karte mit Pfeilfunktion in einer Zeile

this.setState({
    items: this.state.items.map((item, index) =>
      index === 1 ? { ...item, name: 'newName' } : item,
   )
})

1
Dies ist im Grunde identisch mit dieser anderen Antwort, die vor ungefähr einem Jahr veröffentlicht wurde
CertainPerformance

0

Verwenden Sie das Ereignis ein handleChange, um das geänderte Element herauszufinden und es dann zu aktualisieren. Dazu müssen Sie möglicherweise eine Eigenschaft ändern, um sie zu identifizieren und zu aktualisieren.

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


0

Ich würde die Änderung des Funktionshandles verschieben 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 Elemente durchlaufen, können Sie einen zusätzlichen Zähler (im folgenden Code als Index bezeichnet) einfü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)}

Schließlich können Sie Ihren Änderungslistener wie unten gezeigt anrufen

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

Mutieren Sie niemals den Status von React direkt.
Neurotransmitter

0

Wenn Sie nur einen Teil von ändern müssen Array, haben Sie eine Reaktionskomponente mit dem Status auf.

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

Es ist am besten, red-onedas Arraywie 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)

was ist newArray? meinst du newItems? Wenn Sie dies tun, würde das den Staat danach nicht mit nur einem Gegenstand verlassen?
Micnil

Dadurch wird newItemsdem stateObjekt eine neue Eigenschaft hinzugefügt, und die vorhandene itemsEigenschaft wird nicht aktualisiert .
Neurotransmitter

0

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

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

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

    ItemsCopy.push(entry)
})


this.setState({Items:ItemsCopy});

0

Versuchen Sie es mit Code:

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

this.setState({items: cloneObj });

0

Das Befolgen des Codes hat mein langweiliges Gehirn geschont. Entfernen des Objekts und Ersetzen 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

Wie wäre es, eine andere Komponente zu erstellen (für ein Objekt, das in das Array aufgenommen werden muss) und Folgendes als Requisiten zu übergeben?

  1. Komponentenindex - Der Index wird zum Erstellen / Aktualisieren im 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 diese SubObjectForm verwendet wird.

und setSubObjectData kann 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 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
this.setState({
      items: this.state.items.map((item,index) => {
        if (index === 1) {
          item.name = 'newName';
        }
        return item;
      })
    });

1
Dies ist also nicht optimal. Sie iterieren das gesamte Array, um nur das zweite Element zu aktualisieren.
wscourge

Sie mutieren auch das Element im ersten Array, das Sie verwenden solltenitem = Object.assign({}, item, {name: 'newName'});
remram

0

@ JonnyBuchanans Antwort funktioniert einwandfrei, jedoch nur für Array-Statusvariablen. Wenn die Statusvariable nur ein einzelnes Wörterbuch ist, gehen Sie wie folgt vor:

inputChange = input => e => {
    this.setState({
        item: update(this.state.item, {[input]: {$set: e.target.value}})
    })
}

Sie können [input]durch den Feldnamen Ihres Wörterbuchs und e.target.valuedurch dessen Wert ersetzen . Dieser Code führt den Aktualisierungsjob beim Eingabeänderungsereignis meines Formulars aus.


-3

Versuchen Sie dies, es wird definitiv funktionieren, in einem anderen Fall habe ich es versucht, aber es hat nicht funktioniert

import _ from 'lodash';

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

Mutieren Sie niemals den Status von React direkt.
Neurotransmitter

-3
 handleChanges = (value, key) => {
     // clone the current State object
    let cloneObject = _.extend({}, this.state.currentAttribute);
    // key as user.name and value= "ABC" then current attributes have current properties as we changes
    currentAttribute[key] = value;
    // then set the state "currentAttribute" is key and "cloneObject" is changed object.  
    this.setState({currentAttribute: cloneObject});

und Ändern aus Textfeld onChange-Ereignis hinzufügen

onChange = {
   (event) => {                                                
      this.handleChanges(event.target.value, "title");
   }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.