Jak zaktualizować właściwości stanu zagnieżdżonego w React


390

Próbuję uporządkować swój stan za pomocą zagnieżdżonej właściwości w następujący sposób:

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

Ale aktualizowanie takiego stanu,

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

nie działa Jak można to zrobić poprawnie?


4
Co masz na myśli mówiąc, że nie działa? To niezbyt dobre pytanie - co się stało? Jakieś błędy? Jakie błędy?
Darren Sweeney


16
Odpowiedź brzmi: Nie używaj stanu zagnieżdżonego w React . Przeczytaj doskonałą odpowiedź.
Qwerty,

4
Co zamiast tego należy zastosować?
Jānis Elmeris

42
Po prostu nieużywanie stanu zagnieżdżonego jest niedopuszczalną odpowiedzią na to, jak powszechny jest dziś React. Ta sytuacja się pojawi i programiści potrzebują odpowiedzi na to pytanie.
serraosays

Odpowiedzi:


455

W setStateprzypadku zagnieżdżonego obiektu można zastosować poniższe podejście, ponieważ myślę, że setState nie obsługuje zagnieżdżonych aktualizacji.

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

Chodzi o to, aby utworzyć fikcyjny obiekt, wykonać na nim operacje, a następnie zastąpić stan komponentu zaktualizowanym obiektem

Teraz operator rozkładania tworzy tylko jedną zagnieżdżoną kopię obiektu. Jeśli twój stan jest mocno zagnieżdżony, jak:

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

Możesz ustawić Stan używając operatora spreadu na każdym poziomie jak

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

Jednak powyższa składnia staje się coraz bardziej brzydka, ponieważ stan staje się coraz bardziej zagnieżdżony, dlatego polecam użyć immutability-helperpakietu do aktualizacji stanu.

Zobacz tę odpowiedź na temat aktualizacji stanu za pomocą immutability helper.


2
@Ohgodwhy, nie, to nie ma bezpośredniego dostępu do stanu, ponieważ {... this.state.someProperty} ponownie uruchamia nowy obiekt somePropertyi modyfikujemy go
Shubham Khatri

4
to mi nie zadziałało. Używam wersji reagującej @ 15.6.1
Rajiv

5
@Stophface Możemy użyć Lodash do głębokiego klonowania, ale nie wszyscy dołączą tę bibliotekę tylko do ustawieniaState
Shubham Khatri

15
Czy to nie narusza reagujes.org/docs/… ? Jeśli dwie zmiany w zagnieżdżonym obiekcie zostaną sparowane, ostatnia zmiana zastąpi pierwszą. Więc najbardziej poprawny sposób to: this.setState((prevState) => ({nested: {...prevState.nested, propertyToSet: newValue}}) Trochę nudne ...
olejorgenb

2
Nie sądzę, że potrzebujesz tego ...prevState,w ostatnim przykładzie, tylko tysomeProperty i jego dzieci
Jemar Jones

140

Aby napisać w jednym wierszu

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

12
Zalecane jest użycie narzędzia aktualizującego dla setState zamiast bezpośredniego dostępu do this.state w setState. reagjs.org/docs/react-component.html#setstate
Raghu Teja

6
Wierzę, że to mówi, nie czytaj this.stateod razu setStatei oczekuj, że będzie miało nową wartość. W porównaniu do połączeń this.statewewnątrz setState. Czy jest też problem z tym ostatnim, który nie jest dla mnie jasny?
trpt4him

2
Jak powiedział trpt4him , podany przez ciebie link mówi o problemie z dostępem this.statepo nim setState. Poza tym nie przechodzimy this.statedo setState. Te właściwości rozprzestrzeniania operatora. Jest taki sam jak Object.assign (). github.com/tc39/proposal-object-rest-spread
Yoseph

3
@RaghuTeja może właśnie zrobienie this.setState(currentState => { someProperty: { ...currentState.someProperty, flag: false} });może pozwolić uniknąć tego problemu?
Webwoman

Dodałem odpowiedź, jak osiągnąć to samo dzięki haczykowi useState dla kompletności.
Andrew

90

Czasami bezpośrednie odpowiedzi nie są najlepsze :)

Krótka wersja:

ten kod

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

należy uprościć jako coś w tym rodzaju

this.state = {
    somePropertyFlag: true
}

Długa wersja:

Obecnie nie powinieneś chcieć pracować ze stanem zagnieżdżonym w React . Ponieważ React nie jest zorientowany na pracę ze stanami zagnieżdżonymi, a wszystkie proponowane tutaj rozwiązania wyglądają jak hacki. Nie używają frameworka, ale z nim walczą. Sugerują napisanie niezbyt przejrzystego kodu w celu wątpliwego zgrupowania niektórych właściwości. Są więc bardzo interesujące jako odpowiedź na wyzwanie, ale praktycznie bezużyteczne.

Wyobraźmy sobie następujący stan:

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

Co się stanie, jeśli zmienisz tylko wartość child1? React nie renderuje ponownie widoku, ponieważ używa płytkiego porównania i stwierdzi, że parentwłaściwość się nie zmieniła. BTW bezpośrednia mutacja obiektu stanu jest ogólnie uważana za złą praktykę.

Musisz więc ponownie stworzyć całość parent obiekt. Ale w tym przypadku napotkamy inny problem. React pomyśli, że wszystkie dzieci zmieniły swoje wartości i zrenderuje je wszystkie. Oczywiście nie sprzyja to wydajności.

Nadal można rozwiązać ten problem, pisząc skomplikowaną logikę, shouldComponentUpdate()ale wolałbym zatrzymać się tutaj i zastosować proste rozwiązanie z krótkiej wersji.


4
Myślę, że istnieją przypadki użycia, w których stan zagnieżdżenia jest w porządku. Np. Stan dynamicznie śledzący pary klucz-wartość. Zobacz tutaj, jak możesz zaktualizować stan tylko dla jednego wpisu w stanie: reagjs.org/docs/…
pors

1
Płytkie porównanie jest używane domyślnie dla czystych komponentów.
Basel Shishani,

4
Chcę stworzyć ołtarz na waszą cześć i modlić się do niego każdego ranka. Dotarcie do tej odpowiedzi zajęło mi trzy dni, co doskonale wyjaśnia NIEZWYKŁĄ decyzję projektową. Wszyscy po prostu próbują użyć operatora rozprzestrzeniania lub zrobić inne włamania tylko dlatego, że stan zagnieżdżony wygląda bardziej czytelnie dla człowieka.
Edeph

1
Jak zatem sugerujesz, aby użyć React do robienia rzeczy takich jak renderowanie interaktywnego drzewa (na przykład moja firma ma kilka czujników w różnych częściach świata, każdy innego typu, o różnych właściwościach, w różnych lokalizacjach (ponad 80 ), OCZYWIŚCIE, że są zorganizowane w hierarchii, ponieważ każde wdrożenie kosztuje tysiące dolarów i jest kompletnym projektem, więc zarządzanie nimi bez hierarchii byłoby niemożliwe). Jestem ciekawy, jak nie modelowałbyś takich rzeczy jak drzewa jako ... drzewa w React.
DanyAlejandro

10
wygląda na to, że React nie został stworzony do reprezentowania niczego, co jest przechowywane jako rekurencyjna struktura o nietrywialnej głębi. To trochę rozczarowujące, ponieważ widoki drzew pojawiają się w prawie każdej złożonej aplikacji (Gmail, menedżery plików, dowolny system zarządzania dokumentami, ...). Użyłem React do tych rzeczy, ale zawsze miałem gorliwość React, która mówi wszystkim, że robisz anty-wzory i sugeruje, że rozwiązaniem jest utrzymywanie głębokich kopii drzewa w stanie każdego węzła (tak, 80 kopii). Chciałbym zobaczyć nie zhakowany komponent widoku drzewa, który nie łamie żadnych reguł React.
DanyAlejandro

61

Zrzeczenie się

Stan zagnieżdżony w React jest niepoprawny

Przeczytaj tę doskonałą odpowiedź .

 

Uzasadnienie tej odpowiedzi:

React's setState to tylko wbudowana wygoda, ale wkrótce zdajesz sobie sprawę, że ma swoje ograniczenia. Korzystanie z niestandardowych właściwości i inteligentne użycie forceUpdate daje znacznie więcej. na przykład:

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

Na przykład MobX całkowicie wykopuje stan i używa niestandardowych obserwowalnych właściwości.
Użyj składników Obserwowalne zamiast stanu w komponentach React.

 


odpowiedź na twoją nędzę - patrz przykład tutaj

Tam jest inny krótszy sposób aktualizacji dowolnej zagnieżdżonej właściwości.

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

W jednej linii

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

Uwaga: tutaj jest operator przecinkowy ~ MDN , zobacz tutaj w akcji (piaskownica) .

To jest podobne do (chociaż to nie zmienia odniesienia stanu)

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

Dla subtelnej różnicy w tym kontekście między forceUpdateisetState patrz połączony przykład.

Oczywiście narusza to niektóre podstawowe zasady, takie jak state powinno być tylko do odczytu, ale ponieważ natychmiast odrzucasz stary stan i zastępujesz go nowym, jest to całkowicie w porządku.

Ostrzeżenie

Nawet jeśli składnik zawierający stan będzie aktualizować i rerender prawidłowo ( z wyjątkiem tego Gotcha ) , rekwizyty będą nie propagować dzieci (patrz komentarz Spymaster za poniżej) . Użyj tej techniki tylko wtedy, gdy wiesz, co robisz.

Na przykład możesz przekazać zmieniony płaski rekwizyt, który można łatwo zaktualizować i przekazać.

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

Teraz, mimo że odwołanie do complexNestedProp nie uległo zmianie ( shouldComponentUpdate )

this.props.complexNestedProp === nextProps.complexNestedProp

komponent zwróci się ponownie po każdej aktualizacji komponentu nadrzędnego, co ma miejsce po wywołaniu this.setStatelub this.forceUpdatew elemencie nadrzędnym.

Skutki mutowania państwa

Korzystanie stan zagnieżdżony i mutowania stan bezpośrednio jest niebezpieczne, ponieważ różne obiekty mogą posiadać (celowo lub nie) różne (starsze) odniesienia do państwa i niekoniecznie musi wiedzieć, kiedy do aktualizacji (przy użyciu na przykład PureComponentczy shouldComponentUpdatejest realizowany do zwrotu false) OR są przeznaczony do wyświetlania starych danych, jak w poniższym przykładzie.

Wyobraź sobie oś czasu, która ma renderować dane historyczne, mutacja danych pod ręką spowoduje nieoczekiwane zachowanie, ponieważ zmieni także poprzednie elementy.

przepływ państwowy zagnieżdżone w przepływie stanu

W każdym razie tutaj możesz zobaczyć, Nested PureChildClassże nie został ponownie przesłany z powodu braku propagacji rekwizytów.


12
Nigdzie nie powinieneś mutować żadnego stanu reakcji. Jeśli istnieją zagnieżdżone komponenty, które używają danych z state.nested lub state.inother, nie będą renderowane ponownie, podobnie jak ich dzieci. Jest tak, ponieważ obiekt się nie zmienił, dlatego React uważa, że ​​nic się nie zmieniło.
Zeragamba,

@ SpyMaster356 Zaktualizowałem moją odpowiedź, aby rozwiązać ten problem. Należy również pamiętać, że na przykład MobX całkowicie porzuca statei używa niestandardowych obserwowalnych właściwości. React's setStateto tylko wbudowana wygoda, ale wkrótce zdajesz sobie sprawę, że ma swoje ograniczenia. Korzystanie z niestandardowych właściwości i inteligentne użycie forceUpdatedaje znacznie więcej. Użyj składników Obserwowalne zamiast stanu w komponentach React.
Qwerty,

Dodałem również przykład reag-experiments.herokuapp.com/state-flow (link do git u góry)
Qwerty

Co jeśli załadujesz na przykład obiekt z zagnieżdżonymi atrybutami z bazy danych do stanu? Jak w takim przypadku uniknąć stanu zagnieżdżonego?
tobiv

3
@tobiv Lepiej jest przekształcić dane po pobraniu w inną strukturę, ale zależy to od przypadku użycia. Dobrze jest mieć zagnieżdżony obiekt lub tablicę obiektów w stanie, jeśli myślisz o nich jako o zwartych jednostkach niektórych danych. Problem pojawia się, gdy trzeba przechowywać przełączniki interfejsu użytkownika lub inne obserwowalne dane (tj. Dane, które powinny natychmiast zmienić widok), ponieważ muszą być one płaskie, aby poprawnie uruchomić wewnętrzne algorytmy różnicowania React. W przypadku zmian w innych zagnieżdżonych obiektach można łatwo wywołać this.forceUpdate()lub zaimplementować określone shouldComponentUpdate.
Qwerty,

21

Jeśli używasz ES2015, masz dostęp do Object.assign. Możesz użyć go w następujący sposób, aby zaktualizować zagnieżdżony obiekt.

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

Scalasz zaktualizowane właściwości z istniejącymi i używasz zwróconego obiektu do aktualizacji stanu.

Edycja: Dodano pusty obiekt jako cel do funkcji przypisania, aby upewnić się, że stan nie jest mutowany bezpośrednio, jak wskazał carkod.


12
Mogę się mylić, ale nie sądzę, że używasz React bez ES2015.
Madbreaks,

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

1
wydaje się tutaj bardziej eleganckie rozwiązanie, 1) unikając żmudnego rozprzestrzeniania się w setState i 2) ładnie wprowadzając nowy stan do setState, unikając mutowania bezpośrednio wewnątrz setState. Wreszcie, 3) unikanie korzystania z dowolnej biblioteki do prostego zadania
Webwoman

Te dwa przykłady nie są równoważne. Jeśli propertyzawiera kilka zagnieżdżonych właściwości, pierwsza usunie je, a druga nie. odesandbox.io/s/nameless-pine-42thu
Qwerty

Qwerty, masz rację, dziękuję za wysiłek. Usunąłem wersję ES6.
eyecandy.dev,

Proste i proste!
Tony Sepia

Użyłem twojego rozwiązania. Mój stan obj jest niewielki i działał dobrze. Czy React będzie renderował tylko dla zmienionego stanu, czy będzie renderował wszystko?
Kenji Noguchi

14

Istnieje wiele bibliotek, które mogą w tym pomóc. Na przykład za pomocą pomocnika niezmienności :

import update from 'immutability-helper';

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

Za pomocą zestawu lodash / fp :

import {set} from 'lodash/fp';

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

Za pomocą scalania lodash / fp :

import {merge} from 'lodash/fp';

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

Jeśli używasz lodash, prawdopodobnie chcesz spróbować _.cloneDeepustawić stan jako klonowaną kopię.
Yixing Liu

@YixingLiu: Zaktualizowałem odpowiedź do użycia lodash/fp, więc nie musimy się martwić o mutacje.
tokland

Jakieś zalety wyboru mergelub setfunkcji w lodash / fp oprócz składni?
Toivo Säwén

Dobra odpowiedź, być może chcesz dodać, że musisz również zadzwonić this.setState(newState)po metody lodash / fp. Inną gotcha jest to, że lodash .set(non-fp) ma inną kolejność argumentów, co mnie na chwilę potknęło .
Toivo Säwén

7

Używamy Immer https://github.com/mweststrate/immer do obsługi tego rodzaju problemów.

Właśnie zastąpiłem ten kod w jednym z naszych komponentów

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

Z tym

import produce from 'immer';

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

Z zanurzeniem traktujesz swój stan jako „normalny obiekt”. Magia dzieje się za sceną z obiektami proxy.


6

Oto wariant pierwszej odpowiedzi podanej w tym wątku, który nie wymaga żadnych dodatkowych pakietów, bibliotek ani funkcji specjalnych.

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

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

Aby ustawić stan określonego zagnieżdżonego pola, ustawiłeś cały obiekt. Zrobiłem to przez utworzenie zmiennej, newStatei rozprzestrzenia zawartość aktualnego stanu do niego pierwszy pomocą ES2015 operatora spread . Następnie zastąpiłem wartość this.state.flagnową wartością (ponieważ ustawiłem flag: value po rozłożeniu bieżącego stanu na obiekt, flagpole w bieżącym stanie jest zastępowane). Następnie po prostu ustawiam stan somePropertymojego newStateobiektu.


6

Chociaż zagnieżdżanie nie jest tak naprawdę sposobem traktowania stanu komponentu, czasami dla czegoś łatwego dla zagnieżdżania na jednym poziomie.

Dla takiego stanu

state = {
 contact: {
  phone: '888-888-8888',
  email: 'test@test.com'
 }
 address: {
  street:''
 },
 occupation: {
 }
}

Ponownie używana metoda, którą zastosowałem, wyglądałaby tak.

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

następnie przekazując nazwę obj dla każdego zagnieżdżenia, do którego chcesz się adresować ...

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

4

Użyłem tego rozwiązania.

Jeśli masz taki stan zagnieżdżenia:

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

możesz zadeklarować funkcję handleChange, która kopiuje bieżący status i przypisuje go ponownie ze zmienionymi wartościami

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

tutaj HTML z detektorem zdarzeń

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

Potwierdzony. Działa to świetnie, jeśli masz już do czynienia z już ustalonym projektem z zagnieżdżonymi właściwościami.
metal_jacke1

4

Utwórz kopię stanu:

let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))

wprowadź zmiany w tym obiekcie:

someProperty.flag = "false"

teraz zaktualizuj stan

this.setState({someProperty})

setState jest asynchroniczny. Dlatego podczas aktualizacji ktoś inny może wywołać tę funkcję i skopiować nieaktualną wersję stanu.
Aviv Ben Shabat,

3

Chociaż pytano o stan opartego na klasach komponentu React, ten sam problem występuje w przypadku hook useState. Co gorsza: hak useState nie akceptuje częściowych aktualizacji. To pytanie stało się bardzo istotne, gdy wprowadzono hook useState.

Postanowiłem opublikować następującą odpowiedź, aby upewnić się, że pytanie obejmuje bardziej nowoczesne scenariusze, w których używany jest haczyk useState:

Jeśli masz:

const [state, setState] = useState({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })

możesz ustawić zagnieżdżoną właściwość poprzez klonowanie bieżącej i łatanie wymaganych segmentów danych, na przykład:

setState(current => { ...current, someProperty: { ...current.someProperty, flag: false } });

Lub możesz użyć biblioteki Immer, aby uprościć klonowanie i łatanie obiektu.

Możesz też użyć biblioteki Hookstate (oświadczenie: jestem autorem), aby całkowicie zarządzać złożonymi (lokalnymi i globalnymi) danymi stanu i całkowicie poprawić wydajność (czytaj: nie martw się o optymalizację renderowania):

import { useStateLink } from '@hookstate/core' 
const state = useStateLink({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })

uzyskaj pole do renderowania:

state.nested.someProperty.nested.flag.get()
// or 
state.get().someProperty.flag

ustaw zagnieżdżone pole:

state.nested.someProperty.nested.flag.set(false)

Oto przykład Hookstate, w którym stan jest głęboko / rekurencyjnie zagnieżdżony w drzewiastej strukturze danych .


2

Dwie inne opcje jeszcze nie wymienione:

  1. Jeśli masz stan głęboko zagnieżdżony, zastanów się, czy możesz zrestrukturyzować obiekty potomne, aby usiadły w katalogu głównym. Ułatwia to aktualizację danych.
  2. Istnieje wiele przydatnych bibliotek do obsługi niezmiennego stanu wymienionych w dokumentach Redux . Polecam Immer, ponieważ pozwala on na pisanie kodu w sposób mutatywny, ale obsługuje niezbędne klonowanie za kulisami. Zatrzymuje również powstały obiekt, więc nie można później przypadkowo zmutować go.

2

Aby wszystko było ogólne, pracowałem nad odpowiedziami @ ShubhamKhatri i @ Qwerty.

obiekt państwowy

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

kontrola wejściowa

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

metoda updateState

setState as @ ShubhamKhatri odpowiedź

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 jako odpowiedź @ Qwerty

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

Uwaga: powyższe metody nie będą działać dla tablic


2

Bardzo poważnie podchodzę do obaw wyrażonych już wokół tworzenia kompletnej kopii stanu twojego komponentu. Powiedziawszy to, zdecydowanie zalecam Immer .

import produce from 'immer';

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

Powinno to działać w przypadku React.PureComponent(tzn. Porównań stanu płytkiego React), ponieważ Immersprytnie wykorzystuje obiekt proxy do wydajnego kopiowania arbitralnie głębokiego drzewa stanu. Immer jest również bardziej bezpieczny dla typów w porównaniu do bibliotek takich jak Immutability Helper i jest idealny dla użytkowników Javascript i maszynopisu.


Funkcja narzędzia do pisania

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

1
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
Nie sądzę, żeby to było poprawne, ponieważ objjest to tylko odniesienie do stanu, więc poprzez to this.state
mutujesz

0

Okazało się, że to działa dla mnie, mając w moim przypadku formularz projektu, w którym na przykład masz identyfikator i nazwę, a raczej wolę zachować stan dla projektu zagnieżdżonego.

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

Daj mi znać!


0

Coś takiego może wystarczyć

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

Wiem, że to stare pytanie, ale nadal chciałem podzielić się tym, jak to osiągnąłem. Zakładając, że stan w konstruktorze wygląda następująco:

  constructor(props) {
    super(props);

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

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

Moja handleChangefunkcja jest taka:

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

I pamiętaj, aby odpowiednio nazwać dane wejściowe:

<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

Aktualizacje zagnieżdżone wykonuję za pomocą wyszukiwania ograniczonego :

Przykład:

Zagnieżdżone zmienne w stanie:

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

Funkcja:

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

Posługiwać się:

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

0

To jest mój stan początkowy

    const initialStateInput = {
        cabeceraFamilia: {
            familia: '',
            direccion: '',
            telefonos: '',
            email: ''
        },
        motivoConsulta: '',
        fechaHora: '',
        corresponsables: [],
    }

Hak lub możesz go zastąpić stanem (komponent klasy)

const [infoAgendamiento, setInfoAgendamiento] = useState(initialStateInput);

Metoda handleChange

const actualizarState = e => {
    const nameObjects = e.target.name.split('.');
    const newState = setStateNested(infoAgendamiento, nameObjects, e.target.value);
    setInfoAgendamiento({...newState});
};

Metoda ustawiania stanu za pomocą stanów zagnieżdżonych

const setStateNested = (state, nameObjects, value) => {
    let i = 0;
    let operativeState = state;
    if(nameObjects.length > 1){
        for (i = 0; i < nameObjects.length - 1; i++) {
            operativeState = operativeState[nameObjects[i]];
        }
    }
    operativeState[nameObjects[i]] = value;
    return state;
}

Wreszcie to jest wejście, którego używam

<input type="text" className="form-control" name="cabeceraFamilia.direccion" placeholder="Dirección" defaultValue={infoAgendamiento.cabeceraFamilia.direccion} onChange={actualizarState} />

0

Jeśli używasz formik w swoim projekcie, ma to łatwy sposób na obsługę tego. Oto najłatwiejszy sposób na zrobienie z formik.

Najpierw ustaw wartości początkowe w atrybucie formik initivalues ​​lub w reakcji. stan

Tutaj wartości początkowe są definiowane w stanie reakcji

   state = { 
     data: {
        fy: {
            active: "N"
        }
     }
   }

zdefiniuj powyżej wartości początkowe dla pola formik wewnątrz initiValuesatrybutu formik

<Formik
 initialValues={this.state.data}
 onSubmit={(values, actions)=> {...your actions goes here}}
>
{({ isSubmitting }) => (
  <Form>
    <Field type="checkbox" name="fy.active" onChange={(e) => {
      const value = e.target.checked;
      if(value) setFieldValue('fy.active', 'Y')
      else setFieldValue('fy.active', 'N')
    }}/>
  </Form>
)}
</Formik>

Zrób konsolę, aby sprawdzić stan zaktualizowany w stringzamiast funkcji booleanformik, setFieldValueaby ustawić stan lub skorzystaj z narzędzia do debugowania Reaguj, aby zobaczyć zmiany w wartościach stanu formik.


0

spróbuj tego kodu:

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

Spowoduje to jednak usunięcie wszelkich innych właściwości obecnych w obiekcie, do którego istnieje odwołanie, somePropertyi po wykonaniu powyższego kodu flagpozostaną tylko właściwości
Yashas,

0

w książce widziałem:

this.setState(state => state.someProperty.falg = false);

ale nie jestem pewien, czy to prawda ..

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.