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.

wyblakłe
źródło
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]
})
David Hellsing
źródło
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

StateLess
źródło
@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]
}))
Ridd
źródło
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'));
NealeU
źródło
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
17

Jak wspomniano w komentarzu @nilgun, możesz użyć pomocników immutability . Uważam, że jest to bardzo przydatne.

Z dokumentów:

Proste naciśnięcie

var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]

initialArray jest nadal [1, 2, 3].

Clarkie
źródło
6
Pomocniki niezmienności React są opisane w dokumentacji jako przestarzałe. Zamiast tego należy teraz użyć github.com/kolodny/immutability-helper .
Periata Breatta
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
użytkownik3444748
źródło
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);
Radełko
źródło
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})
            })
Rajnikant Lodhi
źródło
0

Pomogło mi to dodać tablicę w tablicy

this.setState(prevState => ({
    component: prevState.component.concat(new Array(['new', 'new']))
}));
MadKad
źródło
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(', '))
        }

}
Ian Smith
źródło
-1
this.setState({
  arrayvar: [...this.state.arrayvar, ...newelement]
})
M.Samiei
źródło
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;
}));
ANUBHAV RAWAT
źródło
-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)})
  })
Hamid Hosseinpour
źródło
2
wciąż mutujesz stan bezpośrednio
Asbar Ali
2
I nie aktualizuje poprawnie stanu składnika, ponieważ .pushzwraca liczbę , a nie tablicę.
Emile Bergeron,