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?

Alex Yong
źródło
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
Spróbuj przeczytać: stackoverflow.com/questions/18933985/...
Andrew Paramoshkin
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.

Shubham Khatri
źródło
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} });
Yoseph
źródło
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.

Konstantin Smolianin
źródło
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.

Qwerty
źródło
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.

Alyssa Roose
źródło
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);
eyecandy.dev
źródło
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},
});
tokland
źródło
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.

Joakim Jäderberg
źródło
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.

Matthew Berkompas
źródło
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: '[email protected]'
 }
 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')}
 />
użytkownik2208124
źródło
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" />
Alberto Piras
źródło
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})
Dhakad
źródło
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 .

Andrzej
źródło
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.
Cory House
źródło
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

Venugopal
źródło
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)}
Stephen Paul
źródło
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)
}
użytkownik3444748
źródło
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ć!

Michael Stokes
źródło
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) 
Eladian
źródło
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"
/>
Elnoor
źródło
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')}
/>
Emisael Carrera
źródło
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} />
Ramiro Carbonell Delgado
źródło
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.

Sharad Sharma
źródło
0

spróbuj tego kodu:

this.setState({ someProperty: {flag: false} });
Soumya
źródło
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 ..

Zroszona Ewa
źródło