Prawidłowa modyfikacja tablic stanu w ReactJS


416

Chcę dodać element na końcu statetablicy, czy to właściwy sposób, aby to zrobić?

this.state.arrayvar.push(newelement);
this.setState({arrayvar:this.state.arrayvar});

Obawiam się, że modyfikowanie tablicy w miejscu pushmoże powodować problemy - czy jest to bezpieczne?

Alternatywa tworzenia kopii tablicy i setStateing wydaje się marnotrawstwem.


9
Myślę, że można użyć pomocników niezmienności. zobacz to: facebook.github.io/react/docs/update.html#simple-push
nilgun

setState w tablicy stanu sprawdź moje rozwiązanie. <br/> <br> stackoverflow.com/a/59711447/9762736
Rajnikant Lodhi

Odpowiedzi:


763

Dokumentacja React mówi:

Traktuj ten stan jak gdyby był niezmienny.

Twój pushbędzie mutować stanu bezpośrednio i które potencjalnie mogłyby prowadzić do błędu kodu na brzuchu, nawet jeśli są „zerowania” stan ponownie później. F.ex, może to prowadzić do tego, że niektóre metody cyklu życia, takie jak componentDidUpdatenie, zostaną uruchomione.

Zalecanym podejściem w późniejszych wersjach React jest użycie funkcji aktualizującej podczas modyfikowania stanów w celu uniknięcia warunków wyścigu:

this.setState(prevState => ({
  arrayvar: [...prevState.arrayvar, newelement]
}))

„Marnotrawstwo” pamięci nie stanowi problemu w porównaniu z błędami, które możesz napotkać przy użyciu niestandardowych modyfikacji stanu.

Alternatywna składnia dla wcześniejszych wersji React

Możesz użyć, concataby uzyskać czystą składnię, ponieważ zwraca ona nową tablicę:

this.setState({ 
  arrayvar: this.state.arrayvar.concat([newelement])
})

W ES6 możesz użyć operatora Spread :

this.setState({
  arrayvar: [...this.state.arrayvar, newelement]
})

8
Czy możesz podać przykład wystąpienia wyścigu?
soundly_typed

3
@Qiming pushzwraca nową długość tablicy, więc nie będzie działać. Ponadto setStatejest asynchroniczny, a React może ustawić w kolejce kilka zmian stanu w jednym przebiegu renderowania.
David Hellsing

2
@mindeavour mówi, że masz ramkę animacji, która szuka parametrów w this.state, oraz inną metodę, która zmienia inne parametry po zmianie stanu. Mogą istnieć pewne ramki, w których stan się zmienił, ale nie znalazł odzwierciedlenia w metodzie, która nasłuchuje zmiany, ponieważ setState jest asynchroniczny.
David Hellsing,

1
@ChristopherCamps Ta odpowiedź nie zachęca do wywoływania setStatedwa razy, pokazuje dwa podobne przykłady ustawiania tablicy stanu bez bezpośredniej mutacji.
David Hellsing

2
Prostym sposobem na traktowanie tablicy stanów jako niezmiennej jest teraz: let list = Array.from(this.state.list); list.push('woo'); this.setState({list});Modyfikuj oczywiście swoje preferencje stylu.
podstawowe

133

Najłatwiej, jeśli używasz ES6.

initialArray = [1, 2, 3];

newArray = [ ...initialArray, 4 ]; // --> [1,2,3,4]

Nowa tablica będzie [1,2,3,4]

zaktualizować swój stan w React

this.setState({
         arrayvar:[...this.state.arrayvar, newelement]
       });

Dowiedz się więcej o destrukcji macierzy


@Muzietto, czy możesz opracować?
StateLess

4
Sednem tego pytania jest zmiana stanu React, a nie modyfikacja tablic. Sam widziałeś mój punkt widzenia i zredagowałeś swoją odpowiedź. To sprawia, że ​​twoja odpowiedź jest istotna. Dobra robota.
Marco Faustinelli

2
Twoje pytania nie dotyczą bezpośrednio pytania OP
StateLess

1
@ChanceSmith: jest również potrzebny w odpowiedzi StateLess . Nie zależą w aktualizacji stanu od samego stanu. Oficjalny dokument: reagjs.org/docs/…
arcol

1
@RayCoder log i sprawdź wartość arrayvar, wygląda na to, że to nie jest tablica.
StateLess,

57

Najprostszy sposób z ES6:

this.setState(prevState => ({
    array: [...prevState.array, newElement]
}))

Przepraszam, w moim przypadku chcę wepchnąć tablicę do tablicy. tableData = [['test','test']]Po wypchnięciu mojej nowej tablicy tableData = [['test','test'],['new','new']]. Jak podbić ten @David i @Ridd
Johncy

@Johncy Jeśli chcesz [['test','test'],['new','new']]spróbować:this.setState({ tableData: [...this.state.tableData, ['new', 'new']]
Ridd

this.setState({ tableData: [...this.state.tableData ,[item.student_name,item.homework_status_name,item.comments===null?'-':item.comments] ] });Wstawia nową tablicę dwa razy. this.state.tableData.push([item.student_name,item.homework_status_name,item.comments===null?'-':item.comments]);Osiąga pożądaną rzecz, jakiej chcę. ale myślę, że to niewłaściwy sposób.
Johncy

26

React może aktualizować wsadowo, dlatego poprawnym podejściem jest zapewnienie setState funkcji, która wykonuje aktualizację.

W przypadku dodatku do aktualizacji React niezawodnie będą działać:

this.setState( state => update(state, {array: {$push: [4]}}) );

lub dla concat ():

this.setState( state => ({
    array: state.array.concat([4])
}));

Poniżej pokazano, co https://jsbin.com/mofekakuqi/7/edit?js,output jako przykład tego, co się stanie, jeśli się pomylisz.

Wywołanie setTimeout () poprawnie dodaje trzy elementy, ponieważ React nie grupuje aktualizacji w ramach wywołania zwrotnego setTimeout (patrz https://groups.google.com/d/msg/reactjs/G6pljvpTGX0/0ihYw2zK9dEJ ).

Buggy onClick doda tylko „Trzeci”, ale naprawiony doda F, S i T zgodnie z oczekiwaniami.

class List extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      array: []
    }

    setTimeout(this.addSome, 500);
  }

  addSome = () => {
      this.setState(
        update(this.state, {array: {$push: ["First"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Second"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Third"]}}));
    };

  addSomeFixed = () => {
      this.setState( state => 
        update(state, {array: {$push: ["F"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["S"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["T"]}}));
    };



  render() {

    const list = this.state.array.map((item, i) => {
      return <li key={i}>{item}</li>
    });
       console.log(this.state);

    return (
      <div className='list'>
        <button onClick={this.addSome}>add three</button>
        <button onClick={this.addSomeFixed}>add three (fixed)</button>
        <ul>
        {list}
        </ul>
      </div>
    );
  }
};


ReactDOM.render(<List />, document.getElementById('app'));

Czy naprawdę jest tak, że tak się dzieje? Jeśli po prostu to zrobimythis.setState( update(this.state, {array: {$push: ["First", "Second", "Third"]}}) )
Albizia

1
@Albizia Myślę, że powinieneś znaleźć kolegę i przedyskutować go z nim. Jeśli wykonujesz tylko jedno połączenie setState, nie występuje problem z grupowaniem. Chodzi o to, aby pokazać, że React aktualizuje partie, więc tak ... naprawdę jest przypadek, który można znaleźć w wersji JSBin powyższego kodu. Prawie wszystkie odpowiedzi w tym wątku nie rozwiązują tego problemu, więc pojawi się dużo kodu, który czasami się nie udaje
NealeU

state.array = state.array.concat([4])mutuje to poprzedni obiekt stanu.
Emile Bergeron,

1
@EmileBergeron Dziękujemy za wytrwałość. W końcu obejrzałem się i zobaczyłem, czego mój mózg nie chce zobaczyć, i sprawdziłem dokumenty, więc będę edytować.
NealeU

1
Dobrze! Naprawdę łatwo się pomylić, ponieważ niezmienność w JS nie jest oczywista (tym bardziej, gdy mamy do czynienia z API biblioteki).
Emile Bergeron


3

Jeśli używasz komponentu funkcjonalnego, użyj tego jak poniżej.

const [chatHistory, setChatHistory] = useState([]); // define the state

const chatHistoryList = [...chatHistory, {'from':'me', 'message':e.target.value}]; // new array need to update
setChatHistory(chatHistoryList); // update the state

1

Na dodany nowy element do tablicy push()powinna być odpowiedź.

W przypadku usuwania elementu i aktualizacji stanu tablicy poniższy kod działa dla mnie. splice(index, 1)nie mogę pracować.

const [arrayState, setArrayState] = React.useState<any[]>([]);
...

// index is the index for the element you want to remove
const newArrayState = arrayState.filter((value, theIndex) => {return index !== theIndex});
setArrayState(newArrayState);

0

Próbuję przekazać wartość w stanie tablicowym i ustawić taką wartość, a także zdefiniować tablicę stanu i wartość wypychaną za pomocą funkcji mapy.

 this.state = {
        createJob: [],
        totalAmount:Number=0
    }


 your_API_JSON_Array.map((_) => {
                this.setState({totalAmount:this.state.totalAmount += _.your_API_JSON.price})
                this.state.createJob.push({ id: _._id, price: _.your_API_JSON.price })
                return this.setState({createJob: this.state.createJob})
            })

0

Pomogło mi to dodać tablicę w tablicy

this.setState(prevState => ({
    component: prevState.component.concat(new Array(['new', 'new']))
}));

0

Oto przykład Reactjs Hook na rok 2020, który moim zdaniem mógłby pomóc innym. Używam go do dodawania nowych wierszy do tabeli Reactjs. Daj mi znać, czy mogę coś poprawić.

Dodanie nowego elementu do funkcjonalnego komponentu stanu:

Zdefiniuj dane stanu:

    const [data, setData] = useState([
        { id: 1, name: 'John', age: 16 },
        { id: 2, name: 'Jane', age: 22 },
        { id: 3, name: 'Josh', age: 21 }
    ]);

Niech przycisk uruchamia funkcję dodawania nowego elementu

<Button
    // pass the current state data to the handleAdd function so we can append to it.
    onClick={() => handleAdd(data)}>
    Add a row
</Button>
function handleAdd(currentData) {

        // return last data array element
        let lastDataObject = currentTableData[currentTableData.length - 1]

        // assign last elements ID to a variable.
        let lastID = Object.values(lastDataObject)[0] 

        // build a new element with a new ID based off the last element in the array
        let newDataElement = {
            id: lastID + 1,
            name: 'Jill',
            age: 55,
        }

        // build a new state object 
        const newStateData = [...currentData, newDataElement ]

        // update the state
        setData(newStateData);

        // print newly updated state
        for (const element of newStateData) {
            console.log('New Data: ' + Object.values(element).join(', '))
        }

}

-1
this.setState({
  arrayvar: [...this.state.arrayvar, ...newelement]
})

2
Dodaj wyjaśnienie, aby uniknąć uznania tego postu za post niskiej jakości stackoverflow.
Harsha Biyani

2
@Kobe to wywołanie „operator rozprzestrzeniania się” w ES6 i dołącz elementy tablicy 2 do tablicy1. dzięki za zdanie (:
M.Samiei

@ M.Samiei Musisz edytować swój post, aby uniknąć głosów negatywnych. Jest niskiej jakości jak zwykły fragment kodu. Również niebezpieczne jest modyfikowanie stanu w ten sposób, ponieważ aktualizacje mogą być grupowane, więc preferowanym sposobem jest np.setState(state => ({arrayvar: [...state.arrayvar, ...newelement]}) );
NealeU

a rozproszenie newelementobiektu w tablicy i TypeErrortak rzuca .
Emile Bergeron,

-1
//------------------code is return in typescript 

const updateMyData1 = (rowIndex:any, columnId:any, value:any) => {

    setItems(old => old.map((row, index) => {
        if (index === rowIndex) {
        return Object.assign(Object.assign({}, old[rowIndex]), { [columnId]: value });
    }
    return row;
}));

-6

Ten kod działa dla mnie:

fetch('http://localhost:8080')
  .then(response => response.json())
  .then(json => {
    this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
  })

2
wciąż mutujesz stan bezpośrednio
Asbar Ali

2
I nie aktualizuje poprawnie stanu składnika, ponieważ .pushzwraca liczbę , a nie tablicę.
Emile Bergeron,
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.