escapecharacter
escapecharacter

Reputation: 965

In React, how to handle state containing arrays?

I'm new to React, so I appreciate any suggestions for how to make this more idiomatic. This is a toy example, but in the real example, my state contains an array of entities. Any change to this array of entities should trigger a re-render. Any user input or other logic may lead to the addition or deletion of multiple entities at a time.

I currently add entities to the state by copying the list of entities, appending the new item, and setting state. This also triggers a re-render, which is what I want:

    let nextEntities = Object.assign([], this.state.entities) as boolean[];
    nextEntities.push(val);
    this.setState({
      entities: nextEntities
    });

However, if I do this multiple times in a row, only the last entity gets added, because this.state.entities isn't up-to-date.

I even tried creating a queue of entities to add, and triggering an additional setState call from setState's callback, but this hits a stack frame limit. I know I could space out multiple adds in a setTimeout callback, but that seems dumb. Is some usage of hooks the answer?

Here's my minimal example: https://codesandbox.io/s/headless-darkness-w1wt1?file=/src/index.tsx:0-1375

import React from "react";
import ReactDOM from "react-dom";

//My React component state has an array of entities in it
interface State {
  entities: boolean[];
}
interface Props {}

export class App extends React.Component<Props, State> {
  constructor(props) {
    super(props);
    this.state = {
      entities: [true, false]
    };
  }

  onClick = () => {
    console.log("onclick! current state:", this.state.entities);
    //based on user input,
    //  multiple items may get added to the array per frame
    this.addToStateList(true);
    this.addToStateList(false);
  };

  addToStateList(val: boolean) {
    let nextEntities = Object.assign([], this.state.entities) as boolean[];
    //TODO: in a non-toy example, we'd do some extra side-effects on each add here.
    nextEntities.push(val);
    this.setState({
      entities: nextEntities
    });
    console.log("just tried to push new value:", val);
  }

  render() {
    let lItems = this.state.entities.map((val: boolean, index: number) => (
      <div style={{ display: "inline" }} key={index}>
        {(val ? "true" : "false") + ", "}
      </div>
    ));
    return (
      <div onClick={this.onClick}>click to test. Current value: {lItems}</div>
    );
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);

One potential suggestion I'm sure you want to provide is to batch the entities to be added, so they all get added in one setState call. In my real case, this is awkward because:

Upvotes: 0

Views: 891

Answers (2)

Riwen
Riwen

Reputation: 5200

Try this.

this.setState({
  entities: [...this.state.entities, val]
});

The syntax is known as a spread operator, if you're unfamiliar with it.

It's a good rule of thumb to avoid mutating objects/arrays when working with React. So, if your app gets complex, there is a very handy library, called immutability-helper.

Upvotes: 1

Mohammad Faisal
Mohammad Faisal

Reputation: 2363

as setState is an async operation ... one possible solution is to use async-await . I am not sure if it's the best way to do it. but it surely gets the job done.

onClick = async () => {
    await this.addToStateList(true);
    await this.addToStateList(false);
  };

Upvotes: 1

Related Questions