React: jak zaktualizować state.item [1] w stanie za pomocą setState?

258

Tworzę aplikację, w której użytkownik może zaprojektować własną formę. Np. Podaj nazwę pola i szczegóły, które inne kolumny powinny zostać uwzględnione.

Komponent jest dostępny tutaj jako JSFiddle .

Mój stan początkowy wygląda następująco:

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

Chcę zaktualizować stan, gdy użytkownik zmieni dowolną z wartości, ale trudno mi celować w odpowiedni obiekt:

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

Jak stworzyć this.setState, aby zaktualizować items[1].name?

martins
źródło
1
jaka jest twoja wybrana odpowiedź na to pytanie?
Braian Mellor,

Odpowiedzi:

126

Możesz użyć updatepomocnika niezmienności do tego :

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

Lub jeśli nie zależy ci na wykrywaniu zmian w tym elemencie shouldComponentUpdate()przy użyciu metody cyklu życia ===, możesz bezpośrednio edytować stan i zmusić komponent do ponownego renderowania - jest to faktycznie to samo, co odpowiedź @limelights, ponieważ jest to wyciąganie obiektu ze stanu i edytowanie go.

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

Dodanie po edycji:

Zapoznaj się z lekcją na temat komunikacji z prostym komponentem ze szkolenia reagowania, aby dowiedzieć się, jak przekazać funkcję wywołania zwrotnego z rodzica zarządzającego stanem do komponentu potomnego, który musi wywołać zmianę stanu.

Jonny Buchanan
źródło
125

Ponieważ w tym wątku jest dużo dezinformacji, oto jak możesz to zrobić bez bibliotek pomocniczych:

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

Możesz połączyć kroki 2 i 3, jeśli chcesz:

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

Lub możesz zrobić wszystko w jednym wierszu:

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

Uwaga: Zrobiłem itemstablicę. OP użył obiektu. Jednak koncepcje są takie same.


Możesz zobaczyć, co się dzieje w twoim terminalu / konsoli:

 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
mpen
źródło
4
@TranslucentCloud O tak, metody pomocnicze są zdecydowanie miłe, ale myślę, że każdy powinien wiedzieć, co się dzieje pod maską :-)
mpen
Dlaczego potrzebujemy kroku 2 i 3? Dlaczego nie możemy mutować klonu bezpośrednio po pierwszym kroku?
Evmorov,
1
@Evmorov Ponieważ nie jest to głęboki klon. Krok 1 to po prostu klonowanie tablicy, a nie obiektów w środku. Innymi słowy, każdy z obiektów w nowej tablicy wciąż „wskazuje” na istniejące obiekty w pamięci - mutacja jednego spowoduje mutację drugiego (są one takie same). Zobacz także items[0] === clone[0]bit w moim przykładzie terminala na dole. Potrójne =sprawdzenie, czy obiekty odnoszą się do tej samej rzeczy.
mpen
Staje się bardziej skomplikowane, jeśli nie znasz indeksu elementu w tablicy.
Ian Warburton,
@IanWarburton Nie bardzo. items.findIndex()powinien to zrobić krótko.
mpen
92

Zła droga!

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

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

Jak zauważyło wielu lepszych programistów w komentarzach: mutowanie stanu jest złe!

Zajęło mi to trochę czasu, aby to rozgryźć. Powyżej działa, ale odbiera moc React. Na przykładcomponentDidUpdate nie zobaczy tego jako aktualizacji, ponieważ jest modyfikowany bezpośrednio.

Tak więc właściwą drogą byłoby:

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [prevState.items[1].name]: e.target.value,
        },
    }));
};
MarvinVK
źródło
25
ES6 tylko dlatego, że używasz „const”?
nkkollaw
49
Czy nie wywołuje items[1].role = e.target.valuestanu mutacji bezpośrednio?
Antony
28
mutujesz państwo, jest to całkowicie sprzeczne z ideą utrzymania stanu niezmiennego, tak jak sugeruje reakcja. To może sprawić ci wiele bólu w dużej aplikacji.
ncubica
8
@MarvinVK, twoja odpowiedź brzmi „Najlepszą praktyką byłoby:”, a następnie użycie „this.forceUpdate ();” co nie jest zalecane, ponieważ może zostać zastąpione przez setState (), patrz facebook.github.io/react/docs/react-component.html#state . Najlepiej to zmienić, aby nie było mylące dla przyszłych czytelników.
James Z.
7
Chciałem tylko zaznaczyć, że wiele komentarzy jest teraz nieistotnych ze względu na zmiany, a właściwy sposób jest właściwie właściwy.
heez
50

Aby zmodyfikować głęboko zagnieżdżonych / zmiennych w React Państwowej, używane są zwykle trzy sposoby: wanilia JavaScript użytkownika Object.assign, niezmienność pomocniczych i cloneDeepod Lodash .

Istnieje również wiele innych mniej popularnych bibliotek stron trzecich, aby to osiągnąć, ale w tej odpowiedzi omówię tylko te trzy opcje. Istnieją również dodatkowe waniliowe metody JavaScript, takie jak rozkładanie tablic (patrz na przykład odpowiedź @ mpen), ale nie są one bardzo intuicyjne, łatwe w użyciu i zdolne do obsługi wszystkich sytuacji manipulacji stanem.

Jak wskazano niezliczoną ilość razy w najczęściej głosowanych komentarzach do odpowiedzi, których autorzy proponują bezpośrednią mutację stanu: po prostu nie rób tego . Jest to wszechobecny anty-wzór React, który nieuchronnie doprowadzi do niepożądanych konsekwencji. Naucz się właściwej drogi.

Porównajmy trzy powszechnie stosowane metody.

Biorąc pod uwagę ten stan struktury obiektu:

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

Możesz użyć następujących metod, aby zaktualizować najbardziej wewnętrznie inner pola bez wpływu na resztę stanu.

1. Object.assign 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>

Należy pamiętać, że Object.assign nie wykona głębokiego klonowania , ponieważ kopiuje tylko wartości właściwości , i dlatego to, co robi, nazywa się płytkim kopiowaniem (patrz komentarze).

Aby to zadziałało, powinniśmy jedynie manipulować właściwościami typów pierwotnych ( outer.inner), czyli ciągów, liczb, boolanów.

W tym przykładzie tworzymy nową stałą ( const newOuter...), używając Object.assign, która tworzy pusty obiekt ( {}), kopiuje do niego outerobiekt ( { inner: 'initial value' }), a następnie kopiuje { inner: 'updated value' } nad nim inny obiekt .

W ten sposób na koniec nowo utworzona newOuterstała będzie miała wartość od { inner: 'updated value' }momentu innerprzesłonięcia właściwości. Jest newOuterto zupełnie nowy obiekt, który nie jest powiązany z obiektem w stanie, więc można go w razie potrzeby mutować, a stan pozostanie taki sam i nie zmieni się do momentu uruchomienia polecenia aktualizacji.

Ostatnią częścią jest użycie setOuter()setera do zastąpienia oryginału outerw stanie nowo utworzonym newOuterobiektem (zmieni się tylko wartość, nazwa właściwości outersię nie zmieni).

Teraz wyobraź sobie, że mamy głębszy stan state = { outer: { inner: { innerMost: 'initial value' } } }. Możemy spróbować utworzyć newOuterobiekt i zapełnić go outertreścią ze stanu, ale Object.assignnie będziemy mogli skopiować innerMostwartości do tego nowo utworzonego newOuterobiektu, ponieważinnerMost jest on zbyt głęboko zagnieżdżony.

Możesz nadal kopiować inner, jak w powyższym przykładzie, ale ponieważ jest to teraz obiekt, a nie prymityw, odwołanie z newOuter.innerzostanie skopiowane do outer.innerniego, co oznacza, że ​​otrzymamy newOuterobiekt lokalny bezpośrednio powiązany z obiektem w stanie .

Oznacza to, że w tym przypadku mutacje lokalnie utworzonego newOuter.innerbędą miały bezpośredni wpływ naouter.inner obiekt (w stanie), ponieważ w rzeczywistości stały się tym samym (w pamięci komputera).

Object.assign dlatego będzie działać tylko wtedy, gdy masz stosunkowo prostą jednopoziomową strukturę stanu głębokiego z najbardziej wewnętrznymi elementami przechowującymi wartości typu pierwotnego.

Jeśli masz głębsze obiekty (2. poziom lub więcej), które powinieneś zaktualizować, nie używaj Object.assign. Ryzykujesz mutacją stanu bezpośrednio.

2. Klon Lodasha

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>

Klon Lodasha jest znacznie prostszy w użyciu. Wykonuje głębokie klonowanie , więc jest solidną opcją, jeśli masz dość złożony stan z wielopoziomowymi obiektami lub tablicami. Tylko cloneDeep()właściwość stanu najwyższego poziomu, mutuj sklonowaną część w dowolny sposób isetOuter() wróć do stanu.

3. pomocnik niezmienności

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/[email protected]"></script>

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

immutability-helperbierze go na zupełnie nowy poziom, a chłodny rzeczą jest to, że może on nie tylko $setwartości do elementów państwowych, ale również $push, $splice, $merge(itd.) im. Tutaj jest lista dostępnych poleceń .

Notatki dodatkowe

Ponownie pamiętaj, że setOutermodyfikuje on tylko właściwości pierwszego poziomu obiektu stanu ( outerw tych przykładach), a nie głęboko zagnieżdżone (outer.inner ). Gdyby zachowywał się inaczej, to pytanie by nie istniało.

Który jest odpowiedni dla twojego projektu?

Jeśli nie chcesz lub nie możesz korzystać z zewnętrznych zależności i masz prostą strukturę stanu , trzymaj się Object.assign.

Jeśli manipulujesz ogromnym i / lub złożonym stanem , Lodash's cloneDeepto mądry wybór.

Jeśli potrzebujesz zaawansowanych możliwości , tj. Jeśli twoja struktura stanu jest złożona i musisz wykonywać na niej wszelkiego rodzaju operacje, spróbuj immutability-helper, jest to bardzo zaawansowane narzędzie, które można wykorzystać do manipulowania stanem.

... czy naprawdę naprawdę musisz to zrobić?

Jeśli przechowujesz złożone dane w stanie React, być może jest to dobry moment, aby pomyśleć o innych sposobach ich obsługi. Ustawienie złożonych obiektów stanu bezpośrednio w komponentach React nie jest prostą operacją i zdecydowanie sugeruję zastanowienie się nad różnymi podejściami.

Najprawdopodobniej lepiej nie trzymaj swoich złożonych danych w sklepie Redux, ustawiając je tam za pomocą reduktorów i / lub sag i uzyskując dostęp do nich za pomocą selektorów.

Neuroprzekaźnik
źródło
Object.assignnie wykonuje głębokiej kopii. Powiedz, jeśli a = {c: {d: 1}}i b = Object.assign({}, a)wtedy wykonasz b.c.d = 4, to a.c.djest zmutowany.
Awol,
Masz rację, wartość 1najbardziej wewnętrznego obiektu ( a.c.d) zostanie zmutowana. Ale jeśli ponownie przypiszesz następcę pierwszego poziomu b, w ten sposób: b.c = {f: 1}odpowiednia część anie zostanie zmutowana (pozostanie {d: 1}). W każdym razie fajny haczyk, od razu zaktualizuję odpowiedź.
Neurotransmitter
To, co zdefiniowałeś, to tak naprawdę shallow copya nie deep copy. Łatwo jest pomylić co shallow copyoznacza. W shallow copy, a !== bale dla każdego klucza z obiektu źródłowego a,a[key] === b[key]
Awol
Tak, wyraźnie wspomniano płytkość Object.assignw odpowiedzi.
Neurotransmitter
JSON.parse(JSON.stringify(object))jest również wariantem głębokiego klonowania. Wydajność jest gorsza niż lodash cloneDeep. Measurethat.net/Benchmarks/Show/2751/0/…
Tylik
35

Miałem ten sam problem. Oto proste rozwiązanie, które działa!

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });
Jonas
źródło
11
@TranslucentCloud - z pewnością nie jest to bezpośrednia mutacja. oryginalna tablica została sklonowana, zmodyfikowana, a następnie stan został ponownie ustawiony za pomocą sklonowanej tablicy.
vsync
@vsync tak, teraz po zredagowaniu oryginalnej odpowiedzi nie jest to wcale mutacja.
Neurotransmitter
2
@TranslucentCloud - Przedtem moja edycja nie miała z tym nic wspólnego, wielkie dzięki za nastawienie. @Jonas tutaj popełnił tylko prosty błąd w swojej odpowiedzi, używając {nawiasów klamrowych zamiast nawiasów, które naprawiłem
vsync
31

Zgodnie z dokumentacją React na temat setState , używanie Object.assignjak sugerują inne odpowiedzi tutaj nie jest idealne. Ze względu na charakter setStateasynchronicznego zachowania kolejne połączenia przy użyciu tej techniki mogą zastąpić poprzednie połączenia, powodując niepożądane skutki.

Zamiast tego doktorzy React zalecają użycie formy aktualizacji, setStatektóra działa w poprzednim stanie. Pamiętaj, że przy aktualizacji tablicy lub obiektu musisz zwrócić nową tablicę lub obiekt, ponieważ React wymaga od nas zachowania niezmienności stanu. Używanie operatora rozprzestrzeniania się składni ES6 do płytkiego kopiowania tablicy, tworzenie lub aktualizowanie właściwości obiektu przy danym indeksie tablicy wyglądałoby następująco:

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})
eicksl
źródło
2
To odpowiednia odpowiedź, jeśli używasz ES6. Jest to zgodne z odpowiedzią @Jonas. Jednak ze względu na wyjaśnienie wyróżnia się.
Sourabh
Tak, ten kod działa idealnie dobrze i bardzo prosty ..
Shoeb Mirza
29

Najpierw weź pożądany element, zmień co chcesz na tym obiekcie i ustaw go z powrotem na stan. Sposób, w jaki używasz stanu tylko przez przekazywanie obiektu, getInitialStatebyłby znacznie łatwiejszy, gdybyś używał obiektu z kluczem.

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

   this.setState({items: items});
}
Henrik Andersson
źródło
3
Nie, ustępuje Uncaught TypeError: Cannot read property 'items' of null.
martiny
Nie, nie będzie. Twój błąd najprawdopodobniej wynika z tego, co robisz getInitialState.
Henrik Andersson,
10
@HenrikAndersson Coś nie pasuje do twojego przykładu. itemsnie jest nigdzie zdefiniowany.
Edward D'Souza,
1
@ EdwardD'Souza Masz absolutną rację! Moja odpowiedź to pokazać, jak należy ją zdefiniować i wykorzystać. Sposób konfiguracji kodu pytającego nie działa w przypadku rzeczy, które chciałby, i dlatego potrzebna jest potrzeba klucza do obiektu.
Henrik Andersson
7
Jest to typowy anty-wzorzec React, przypisujesz itemodwołanie do własnej this.state.items[1]zmiennej stanu. Następnie modyfikujesz item( item.name = 'newName'), a tym samym mutujesz stan bezpośrednio, co jest wysoce odradzane. W twoim przykładzie nie ma nawet potrzeby dzwonienia this.setState({items: items}), ponieważ stan jest już zmutowany bezpośrednio.
Neurotransmitter
22

Nie mutuj stanu na miejscu. Może to powodować nieoczekiwane wyniki. Nauczyłem się mojej lekcji! Zawsze pracuj z kopią / klonem, Object.assign()jest dobry:

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

Jean Luo
źródło
8
co jest itemsw twoim przykładzie? Miałeś na myśli this.state.itemsczy coś innego?
Buh Buh
Testowałem to, ale brakuje mu jednej linii. Powyżej items[1] = item;powinna znajdować się linijka items = this.state.items;. Uważaj, mój javascript jest zardzewiały i uczę się reagować na mój domowy projekt, więc nie mam pojęcia, czy to dobrze, czy źle :-)
Greg0ry
4

To jest naprawdę proste.

Najpierw wyciągnij cały obiekt pozycji ze stanu, zaktualizuj część obiektu pozycji zgodnie z potrzebami i ustaw cały obiekt pozycji z powrotem w stanie za pośrednictwem setState.

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
}
Jan
źródło
„Object.assign będzie działać, jeśli masz relatywnie prostą jednopoziomową strukturę głębokiego stanu z najbardziej wewnętrznymi elementami posiadającymi wartości typu pierwotnego”.
Neurotransmitter
3

Ponieważ żadna z powyższych opcji nie była dla mnie idealna, użyłem mapy:

this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })
Santiago M. Quintero
źródło
2

Bez mutacji:

// 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)
John Wundes
źródło
2
Nie widzę, gdzie newItemsjest ustawiony.
Neurotransmitter
Czy mapa plus porównanie w tablicy nie jest straszna pod względem wydajności?
Natassia Tavares,
@NatassiaTavares .. co? może jesteś zdezorientowany fo oflub forEachmapa jest najszybsza.
Deano,
1

Okazało się, że jest to zaskakująco trudne i żadna z magii ES6 nie działała zgodnie z oczekiwaniami. Używał takiej struktury, aby uzyskać renderowane właściwości elementu na potrzeby układu.

stwierdzono, że updatemetoda z immutability-helperjest najprostsza w tym uproszczonym przykładzie:

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

zgodnie z adaptacją https://github.com/kolodny/immutability-helper#computed-property-names

elementu tablicy, który ma zostać zaktualizowany, jest bardziej zagnieżdżony obiekt złożony, użyj odpowiedniej metody głębokiej kopii oparciu o złożoność

Z pewnością istnieją lepsze sposoby obsługi parametrów układu, ale chodzi o to, jak obsługiwać tablice. Odpowiednie wartości dla każdego elementu potomnego można również obliczyć poza nimi, ale uważam, że wygodniej jest przekazać parametr ContainState w dół, aby dzieci mogły pobierać właściwości do woli i aktualizować macierz stanów nadrzędnych przy danym indeksie.

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>
    )
  }
}
mxfh
źródło
1

Użyj mapy tablic z funkcją strzałki w jednym wierszu

this.setState({
    items: this.state.items.map((item, index) =>
      index === 1 ? { ...item, name: 'newName' } : item,
   )
})
Rasta_boy
źródło
1
Jest to w zasadzie identyczna z inną odpowiedzią opublikowaną około rok temu
CertainPerformance
0

Użyj zdarzenia, handleChangeaby dowiedzieć się, który element się zmienił, a następnie zaktualizuj go. W tym celu konieczna może być zmiana niektórych właściwości w celu ich identyfikacji i aktualizacji.

Zobacz skrzypce https://jsfiddle.net/69z2wepo/6164/

parser strun
źródło
0

Chciałbym przesunąć zmianę uchwytu funkcji i dodać parametr indeksu

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

do komponentu formularza dynamicznego i przekaż go do komponentu PopulateAtCheckboxes jako rekwizyt. Gdy zapętlasz swoje przedmioty, możesz dołączyć dodatkowy licznik (zwany indeksem w poniższym kodzie), który zostanie przekazany do zmiany uchwytu, jak pokazano poniżej

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

Wreszcie możesz zadzwonić do nasłuchiwania zmian, jak pokazano poniżej

<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>
Kenneth Njendu
źródło
Nigdy nie mutuj bezpośrednio stanu Reacta.
Neurotransmitter
0

Jeśli chcesz zmienić tylko część Array, masz komponent reagujący ze stanem ustawionym na.

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

Najlepiej zaktualizować red-onew Arraynastępujący sposób:

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)
att
źródło
co jest newArray? masz na myśli newItems? Jeśli tak, czy nie opuściłoby to stanu z tylko jednym przedmiotem później?
micnil
Spowoduje to wprowadzenie nowej właściwości newItemsdo stateobiektu i nie zaktualizuje istniejącej itemswłaściwości.
Neuroprzekaźnik
0

lub jeśli masz dynamicznie generowaną listę i nie znasz indeksu, ale po prostu masz klucz lub identyfikator:

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

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

    ItemsCopy.push(entry)
})


this.setState({Items:ItemsCopy});
Jared
źródło
0

Spróbuj z kodem:

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

this.setState({items: cloneObj });
szukanie 9x
źródło
0

Następujący fragment kodu poszedł spokojnie na mój tępy mózg. Usunięcie obiektu i zastąpienie go zaktualizowanym

    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)
    }));
Shan
źródło
0

Co powiesz na utworzenie kolejnego komponentu (dla obiektu, który musi wejść do tablicy) i przekazania następujących jako rekwizytów?

  1. indeks komponentu - indeks zostanie użyty do utworzenia / aktualizacji tablicy.
  2. set function - Ta funkcja umieszcza dane w tablicy na podstawie indeksu komponentu.
<SubObjectForm setData={this.setSubObjectData}                                                            objectIndex={index}/>

Tutaj {indeks} można przekazać na podstawie pozycji, w której używany jest ten SubObjectForm.

i setSubObjectData może być mniej więcej taki.

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

W SubObjectForm, this.props.setData można wywoływać przy zmianie danych, jak podano poniżej.

<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>
Rajkumar Nagarajan
źródło
0
this.setState({
      items: this.state.items.map((item,index) => {
        if (index === 1) {
          item.name = 'newName';
        }
        return item;
      })
    });
Rao Khurram Adeel
źródło
1
to nie jest optymalne, iterujesz całą tablicę, aby zaktualizować tylko drugi element?
wscourge
item = Object.assign({}, item, {name: 'newName'});
Zmieniasz
0

@ Odpowiedź JonnyBuchanana działa idealnie, ale tylko dla zmiennej stanu tablicy. Jeśli zmienna stanu jest tylko jednym słownikiem, wykonaj następujące czynności:

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

Możesz zastąpić [input]nazwą pola swojego słownika i e.target.valuejego wartością. Ten kod wykonuje zadanie aktualizacji w przypadku zmiany danych wejściowych w moim formularzu.

Serge Kishiko
źródło
-3

Wypróbuj to na pewno zadziała, w innym przypadku próbowałem, ale nie działałem

import _ from 'lodash';

this.state.var_name  = _.assign(this.state.var_name, {
   obj_prop: 'changed_value',
});
nirav jobanputra
źródło
Nigdy nie mutuj bezpośrednio stanu Reacta.
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});

i Zmień z pola tekstowego dodaj zdarzenie onChange

onChange = {
   (event) => {                                                
      this.handleChanges(event.target.value, "title");
   }
}
Praveen Patel
źródło