wake-up-neo.com

So aktualisieren Sie Eigenschaften für verschachtelte Status in React

Ich versuche, meinen Zustand zu organisieren, indem ich verschachtelte Eigenschaften wie diese verwende:

this.state = {
   someProperty: {
      flag:true
   }
}

Aber Aktualisierung Zustand wie folgt,

this.setState({ someProperty.flag: false });

funktioniert nicht Wie kann das richtig gemacht werden?

249
Alex Yong

Um setState für ein verschachteltes Objekt zu erstellen, können Sie den folgenden Ansatz verwenden, da setState meines Erachtens keine verschachtelten Aktualisierungen verarbeitet.

_var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})
_

Die Idee ist, ein Dummy-Objekt zu erstellen, das Operationen ausführt und dann den Status der Komponente durch das aktualisierte Objekt ersetzt

Der Spread-Operator erstellt jetzt nur eine verschachtelte Kopie des Objekts. Wenn Ihr Bundesstaat stark verschachtelt ist wie:

_this.state = {
   someProperty: {
      someOtherProperty: {
          anotherProperty: {
             flag: true
          }
          ..
      }
      ...
   }
   ...
}
_

Sie können den Status mithilfe des Spread-Operators auf jeder Ebene wie folgt festlegen

_this.setState(prevState => ({
    ...prevState,
    someProperty: {
        ...prevState.someProperty,
        someOtherProperty: {
            ...prevState.someProperty.someOtherProperty, 
            anotherProperty: {
               ...prevState.someProperty.someOtherProperty.anotherProperty,
               flag: false
            }
        }
    }
}))
_

Die obige Syntax wird jedoch mit zunehmender Verschachtelung des Status immer hässlicher. Daher empfehle ich Ihnen, das Paket immutability-helper zu verwenden, um den Status zu aktualisieren.

In dieser Antwort erfahren Sie, wie Sie den Status mit immutability helper aktualisieren.

310
Shubham Khatri

Um es in einer Zeile zu schreiben

this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
105
Yoseph

Manchmal sind direkte Antworten nicht die besten:)

Kurzfassung:

dieser Code

this.state = {
    someProperty: {
        flag: true
    }
}

sollte so etwas wie vereinfacht werden

this.state = {
    somePropertyFlag: true
}

Lange Version:

Derzeit Sie sollten nicht mit verschachtelten Status in React arbeiten wollen. Da React nicht auf die Arbeit mit verschachtelten Zuständen ausgerichtet ist und alle hier vorgeschlagenen Lösungen als Hacks angezeigt werden. Sie nutzen das Framework nicht, sondern kämpfen damit. Sie schlagen vor, nicht so klaren Code zu schreiben, um bestimmte Eigenschaften zweifelhaft zu gruppieren. Sie sind also als Antwort auf die Herausforderung sehr interessant, aber praktisch nutzlos.

Stellen wir uns folgenden Zustand vor:

{
    parent: {
        child1: 'value 1',
        child2: 'value 2',
        ...
        child100: 'value 100'
    }
}

Was passiert, wenn Sie nur einen Wert von child1 ändern? React rendert die Ansicht nicht erneut, da ein flacher Vergleich verwendet wird und sich die Eigenschaft parent nicht geändert hat. Übrigens wird das direkte Mutieren des Zustandsobjekts im Allgemeinen als schlechte Praxis angesehen.

Sie müssen also das gesamte Objekt parent neu erstellen. In diesem Fall werden wir jedoch auf ein anderes Problem stoßen. React wird denken, dass alle Kinder ihre Werte geändert haben, und wird sie alle neu rendern. Natürlich ist es nicht gut für die Leistung.

Es ist immer noch möglich, dieses Problem durch Schreiben einer komplizierten Logik in shouldComponentUpdate() zu lösen, aber ich würde es vorziehen, hier anzuhalten und eine einfache Lösung aus der Kurzversion zu verwenden.

64

Haftungsausschluss

Verschachtelter Status in React ist falsch

Lesen Sie diese ausgezeichnete Antwort .

Begründung hinter dieser Antwort:

Reacts setState ist nur ein integrierter Komfort, aber Sie werden schnell feststellen, dass er seine Grenzen hat. Durch die Verwendung benutzerdefinierter Eigenschaften und den intelligenten Einsatz von forceUpdate erhalten Sie viel mehr. z.B:

class MyClass extends React.Component {
    myState = someObject
    inputValue = 42
...

MobX, zum Beispiel, gräbt den Status vollständig aus und verwendet benutzerdefinierte beobachtbare Eigenschaften.
Verwenden Sie Observables anstelle von state in React Komponenten.


die Antwort auf dein Elend - siehe Beispiel hier

Es gibt eine andere kürzere Möglichkeit, verschachtelte Eigenschaften zu aktualisieren.

this.setState(state => {
  state.nested.flag = false
  state.another.deep.prop = true
  return state
})

In einer Zeile

this.setState(state => (state.nested.flag = false, state))

Dies ist praktisch dasselbe wie

this.state.nested.flag = false
this.forceUpdate()

Für den subtilen Unterschied in diesem Zusammenhang zwischen forceUpdate und setState siehe das verknüpfte Beispiel.

Dies missbraucht natürlich einige Kernprinzipien, da state nur lesbar sein sollte, aber da Sie den alten Zustand sofort verwerfen und durch einen neuen Zustand ersetzen, ist dies völlig in Ordnung.

Warnung

Auch wenn die Komponente, die den Status wird enthält, ordnungsgemäß aktualisiert und neu gerendert wird ( mit Ausnahme dieses Punktes ), werden die Requisiten fehlschlagen an Kinder zu verbreiten (siehe Spymasters Kommentar unten). Wenden Sie diese Technik nur an, wenn Sie wissen, was Sie tun.

Beispielsweise können Sie eine geänderte flache Requisite übergeben, die aktualisiert und problemlos übergeben wird.

render(
  //some complex render with your nested state
  <ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)

Jetzt, obwohl sich die Referenz für complexNestedProp nicht geändert hat ( shouldComponentUpdate )

this.props.complexNestedProp === nextProps.complexNestedProp

die Komponente wird wird immer dann neu gerendert, wenn die übergeordnete Komponente aktualisiert wird. Dies ist der Fall, wenn this.setState oder this.forceUpdate in der übergeordneten Komponente aufgerufen wurde.

Auswirkungen der Mutation des Staates

Das Verwenden von verschachtelten Status und das direkte Mutieren des Status ist gefährlich, da unterschiedliche Objekte (absichtlich oder nicht) unterschiedliche (ältere) Verweise auf den Status und wissen möglicherweise nicht unbedingt, wann eine Aktualisierung durchgeführt werden muss (z. B. bei Verwendung von PureComponent oder wenn shouldComponentUpdate implementiert ist, um false zurückzugeben) ODER sollen alte Daten wie im folgenden Beispiel anzeigen.

Stellen Sie sich eine Zeitachse vor, die historische Daten rendern soll. Wenn Sie die Daten unter der Hand ändern, wird dies zu unerwartetem Verhalten führen, da auch frühere Elemente geändert werden.

state-flowstate-flow-nested

Wie auch immer, Sie können hier sehen, dass Nested PureChildClass aufgrund fehlender Propagierung der Requisiten nicht erneut gerendert wurde.

47
Qwerty

Wenn Sie ES2015 verwenden, haben Sie Zugriff auf Object.assign. Sie können es folgendermaßen verwenden, um ein verschachteltes Objekt zu aktualisieren.

this.setState({
  someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});

Sie führen die aktualisierten Eigenschaften mit den vorhandenen zusammen und aktualisieren den Status mithilfe des zurückgegebenen Objekts.

Bearbeiten: Der Zuweisungsfunktion wurde ein leeres Objekt als Ziel hinzugefügt, um sicherzustellen, dass der Status nicht direkt mutiert wird, wie carkod hervorhob.

21
Alyssa Roose

Es gibt viele Bibliotheken, die dabei helfen. Zum Beispiel mit nveränderlichkeitshilfe :

import update from 'immutability-helper';

const newState = update(this.state, {
  someProperty: {flag: {$set: false}},
};
this.setState(newState);

Mit lodash/fp setze:

import {set} from 'lodash/fp';

const newState = set(["someProperty", "flag"], false, this.state);

Mit lodash/fp merge:

import {merge} from 'lodash/fp';

const newState = merge(this.state, {
  someProperty: {flag: false},
});
13
tokland

Mit ES6:

this.setState({...this.state, property: {nestedProperty: "new value"}})

Welches ist gleichbedeutend mit:

const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);
12
enzolito

Wir verwenden Immer https://github.com/mweststrate/immer , um diese Art von Problemen zu behandeln.

Ersetzte gerade diesen Code in einer unserer Komponenten

this.setState(prevState => ({
   ...prevState,
        preferences: {
            ...prevState.preferences,
            [key]: newValue
        }
}));

Mit diesem

import produce from 'immer';

this.setState(produce(draft => {
    draft.preferences[key] = newValue;
}));

Mit immer behandeln Sie Ihren Zustand als "normales Objekt". Die Magie geschieht hinter den Kulissen mit Proxy-Objekten.

6

Hier ist eine Variation der ersten Antwort in diesem Thread, für die keine zusätzlichen Pakete, Bibliotheken oder Sonderfunktionen erforderlich sind.

state = {
  someProperty: {
    flag: 'string'
  }
}

handleChange = (value) => {
  const newState = {...this.state.someProperty, flag: value}
  this.setState({ someProperty: newState })
}

Um den Status eines bestimmten verschachtelten Felds festzulegen, haben Sie das gesamte Objekt festgelegt. Dazu habe ich die Variable newState erstellt und den Inhalt des aktuellen Status darin verteilt first ​​mithilfe des ES2015 spread operator . Dann habe ich den Wert von this.state.flag durch den neuen Wert ersetzt (da ich flag: valuenach gesetzt habe, habe ich den aktuellen Status in das Objekt, das Feld flag in, verteilt Der aktuelle Status wird überschrieben. Dann setze ich einfach den Status von someProperty auf mein newState Objekt.

5

Ich habe diese Lösung benutzt.

Wenn Sie einen verschachtelten Status wie diesen haben:

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

sie können die Funktion handleChange deklarieren, die den aktuellen Status kopiert und ihn mit geänderten Werten neu zuweist

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

hier das html mit dem eventlistener

<input type="text" onChange={this.handleChange} " name="friendName" />
3
Alberto Piras

Erstellen Sie eine Kopie des Staates:
let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))

änderungen an diesem Objekt vornehmen:
someProperty.flag = "false"

aktualisieren Sie jetzt den Status
this.setState({someProperty})

3
Dhakad

Obwohl das Verschachteln nicht wirklich so ist, wie Sie einen Komponentenzustand behandeln sollten, kann dies für das einstufige Verschachteln hilfreich sein.

Für einen Staat wie diesen

state = {
 contact: {
  phone: '888-888-8888',
  email: '[email protected]'
 }
 address: {
  street:''
 },
 occupation: {
 }
}

Eine wiederverwendbare Methode würde so aussehen.

handleChange = (obj) => e => {
  let x = this.state[obj];
  x[e.target.name] = e.target.value;
  this.setState({ [obj]: x });
};

dann geben Sie einfach den obj-Namen für jede Verschachtelung ein, die Sie ansprechen möchten ...

<TextField
 name="street"
 onChange={handleChange('address')}
 />
3
user2208124

Um die Dinge allgemeiner zu gestalten, habe ich an den Antworten von @ ShubhamKhatri und @ Qwerty gearbeitet.

Statusobjekt

this.state = {
  name: '',
  grandParent: {
    parent1: {
      child: ''
    },
    parent2: {
      child: ''
    }
  }
};

Eingabesteuerung

<input
  value={this.state.name}
  onChange={this.updateState}
  type="text"
  name="name"
/>
<input
  value={this.state.grandParent.parent1.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent1.child"
/>
<input
  value={this.state.grandParent.parent2.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent2.child"
/>

pdateState-Methode

setState als @ ShubhamKhatri Antwort

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const oldstate = this.state;
  const newstate = { ...oldstate };
  let newStateLevel = newstate;
  let oldStateLevel = oldstate;

  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      newStateLevel[path[i]] = event.target.value;
    } else {
      newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
      oldStateLevel = oldStateLevel[path[i]];
      newStateLevel = newStateLevel[path[i]];
    }
  }
  this.setState(newstate);
}

setState als @ Qwertys Antwort

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const state = { ...this.state };
  let ref = state;
  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      ref[path[i]] = event.target.value;
    } else {
      ref = ref[path[i]];
    }
  }
  this.setState(state);
}

Hinweis: Diese oben genannten Methoden funktionieren nicht für Arrays

2
Venugopal

Ich nehme die Bedenken sehr ernst, die bereits beim Erstellen einer vollständigen Kopie Ihres Komponentenzustands geäußert wurden . Vor diesem Hintergrund würde ich dringend empfehlen Immer.

import produce from 'immer';

<Input
  value={this.state.form.username}
  onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />

Dies sollte für React.PureComponent (d. H. Vergleiche flacher Zustände nach React) funktionieren, da Immer ein Proxy-Objekt geschickt verwendet, um einen beliebig tiefen Zustandsbaum effizient zu kopieren. Immer ist auch typsicherer als Bibliotheken wie Immutability Helper und eignet sich sowohl für Javascript- als auch für TypeScript-Benutzer.


TypeScript Dienstprogrammfunktion

function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s: 
Draft<Readonly<S>>) => any) {
  comp.setState(produce(comp.state, s => { fn(s); }))
}

onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}
2
Stephen Paul

Zwei weitere Optionen, die noch nicht erwähnt wurden:

  1. Wenn Sie einen tief verschachtelten Status haben, sollten Sie überlegen, ob Sie die untergeordneten Objekte so umstrukturieren können, dass sie sich an der Wurzel befinden. Dies erleichtert die Aktualisierung der Daten.
  2. Es gibt viele nützliche Bibliotheken für den Umgang mit unveränderlichen Zuständen in den Redux-Dokumenten aufgeführt . Ich empfehle Immer, da es Ihnen erlaubt, Code mutativ zu schreiben, aber das notwendige Klonen hinter den Kulissen erledigt. Außerdem wird das resultierende Objekt eingefroren, sodass Sie es später nicht versehentlich ändern können.
2
Cory House
stateUpdate = () => {
    let obj = this.state;
    if(this.props.v12_data.values.email) {
      obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
    }
    this.setState(obj)
}
1
user3444748

Ich weiß, dass es eine alte Frage ist, wollte aber trotzdem mitteilen, wie ich dies erreicht habe. Angenommen, der Status im Konstruktor sieht folgendermaßen aus:

  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      user: {
        email: ""
      },
      organization: {
        name: ""
      }
    };

    this.handleChange = this.handleChange.bind(this);
  }

Meine handleChange -Funktion sieht folgendermaßen aus:

  handleChange(e) {
    const names = e.target.name.split(".");
    const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
    this.setState((state) => {
      state[names[0]][names[1]] = value;
      return {[names[0]]: state[names[0]]};
    });
  }

Und stellen Sie sicher, dass Sie die Eingaben entsprechend benennen:

<input
   type="text"
   name="user.email"
   onChange={this.handleChange}
   value={this.state.user.firstName}
   placeholder="Email Address"
/>

<input
   type="text"
   name="organization.name"
   onChange={this.handleChange}
   value={this.state.organization.name}
   placeholder="Organization Name"
/>
0
Elnoor

Ich fand, dass dies für mich funktioniert, da ich in meinem Fall ein Projektformular habe, in dem Sie beispielsweise eine ID und einen Namen haben und den Status für ein verschachteltes Projekt lieber beibehalten möchten.

return (
  <div>
      <h2>Project Details</h2>
      <form>
        <Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} />
        <Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} />
      </form> 
  </div>
)

Gib mir Bescheid!

0
Michael Stokes

Ich habe Updates mit einem verkleinern verschachtelt suchen:

Beispiel:

Die verschachtelten Variablen in state:

state = {
    coords: {
        x: 0,
        y: 0,
        z: 0
    }
}

Die Funktion:

handleChange = nestedAttr => event => {
  const { target: { value } } = event;
  const attrs = nestedAttr.split('.');

  let stateVar = this.state[attrs[0]];
  if(attrs.length>1)
    attrs.reduce((a,b,index,arr)=>{
      if(index==arr.length-1)
        a[b] = value;
      else if(a[b]!=null)
        return a[b]
      else
        return a;
    },stateVar);
  else
    stateVar = value;

  this.setState({[attrs[0]]: stateVar})
}

Verwenden Sie:

<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>
0
Emisael Carrera

So etwas könnte ausreichen,

const isObject = (thing) => {
    if(thing && 
        typeof thing === 'object' &&
        typeof thing !== null
        && !(Array.isArray(thing))
    ){
        return true;
    }
    return false;
}

/*
  Call with an array containing the path to the property you want to access
  And the current component/redux state.

  For example if we want to update `hello` within the following obj
  const obj = {
     somePrimitive:false,
     someNestedObj:{
        hello:1
     }
  }

  we would do :
  //clone the object
  const cloned = clone(['someNestedObj','hello'],obj)
  //Set the new value
  cloned.someNestedObj.hello = 5;

*/
const clone = (arr, state) => {
    let clonedObj = {...state}
    const originalObj = clonedObj;
    arr.forEach(property => {
        if(!(property in clonedObj)){
            throw new Error('State missing property')
        }

        if(isObject(clonedObj[property])){
            clonedObj[property] = {...originalObj[property]};
            clonedObj = clonedObj[property];
        }
    })
    return originalObj;
}

const nestedObj = {
    someProperty:true,
    someNestedObj:{
        someOtherProperty:true
    }
}

const clonedObj = clone(['someProperty'], nestedObj);
console.log(clonedObj === nestedObj) //returns false
console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true

console.log()
const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
console.log(clonedObj2 === nestedObj) // returns false
console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
//returns true (doesn't attempt to clone because its primitive type)
console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty) 
0
Eladian