React - update nested state (Class Component)

Viewed 48

I'm trying to update a nested state. See below. The problem is that upon clicking on a category checkbox, instead of updating the {categories: ....} object in state, it creates a new object in state:

class AppBC extends React.Component {
  constructor(props) {
    super(props)
    this.state = { 
      products: [],
      categories: []
    }
    this.handleSelectCategory = this.handleSelectCategory.bind(this);
  }

  componentDidMount() {
    this.setState({
      products: data_products,
      categories: data_categories.map(category => ({
        ...category,
        selected: true
      }))
    });
  }

  handleSelectCategory(id) {
    this.setState(prevState => ({
      ...prevState.categories.map(
        category => {
          if(category.id === id){
            return {
              ...category,
              selected: !category.selected,
            }
          }else{
            return category;
          } // else
          } // category
          ) // map
        }) // prevState function
    ) // setState
  } // handleSelectCategory


  render() {
    return(
      <div className="bc">
        <h1>Bare Class Component</h1>
        <div className="main-area">
          <Products categories={this.state.categories} products={this.state.products} />
          <Categories 
            categories={this.state.categories}
            handleSelectCategory={this.handleSelectCategory}
          />
        </div>
      </div>
    );
  };

Initial state before clicking (all categories are selected): enter image description here

After clicking on an a checkbox to select a particular category, it saves a new object to state (correctly reflecting the category selection) instead of updating the already existin categories property:

enter image description here

3 Answers

Change your update to:

handleSelectCategory(id) {
    this.setState(prevState => ({
            ...prevState,
            categories: prevstate.categories.map(
                category => {
                    if (category.id === id) {
                        return {
                            ...category,
                            selected: !category.selected,
                        }
                    } else {
                        return category;
                    } // else
                } // category
            ) // map
        }) // prevState function
    ) // setState
}

I prefer this way, it's more easy for reading

handleSelectCategory(id) {
    const index = this.state.categories.findIndex(c => c.id === id);
    const categories = [...this.state.categories];
    categories[index].selected = !categories[index].selected;
    this.setState({ categories });
}

If your purpose is to only change selected property on handleSelectCategory function,

Then you could just do it like

  1. run findIndex on array and obtain index for id match from array of objects.
  2. update selected property for that index

Code:

handleSelectCategory(id) {
  let targetIndex = this.state.categories.findIndex((i) => i.id === id);
  let updatedCategories = [...this.state.categories];
  if (targetIndex !== -1) {
    // this means there is a match
    updatedCategories[targetIndex].selected = !updatedCategories[targetIndex].selected;
    this.setState({
      categories: updatedCategories,
    });
  } else {
    // avoid any operation here if there is no "id" matched
  }
}
Related