young ban
young ban

Reputation: 169

How to use setState in map function

I'm trying to use setState in map function what I want to make when I hit click button that makes states changed

I searched "In map function changed setState" in stackoverflow

but It was hard to understand..and apply my case

this.state = {
      categories: [
        {
          category: 'sports',
          ariticles: [],
          isClick: true
        },

...
...

 return (
      <div>
        <hr></hr>

        <div className="categoryBtn">
          {this.state.map(item => (
            <button
              onClick={
                () => this.setState(() => (item.isClick = false))
                // () => console.log((item.isClick = false))
                // this.setState(() => (item.isClick = !item.isClick))
              }
            >
              {' '}
              {item.category}
            </button>
          ))}

when I hit my button I got error ' this.state.map is not a function'

I expect when I hit button, isClick will be toggle

Upvotes: 3

Views: 2577

Answers (2)

dance2die
dance2die

Reputation: 36895

Your this.state refers to an object containing an array,

categories: [...]

So you'd need to map over this.state.categories.map instead of this.state.map


And also, I've noticed that button event handler mutates the state.

<button onClick={() => this.setState(() => (item.isClick = false))}>
  {item.category}
</button>

You'd need to return a new item reference to notify react that the state has been changed as well as adding a key prop.

Thankfully, someone asked a question about the reason for using key (only 2 hours ago) and replied, which you can refer to.

https://stackoverflow.com/a/57838612/4035

<button key={item.category} onClick={() => this.setState({
    find the item by searching for each item in `this.state.cateogies`
  })}>
  {item.category}
</button>;

But then it gets tough to find the right element, so I'd recommend to normalize(meaning change the this.state.categories) too look like following for easier way to set the state.

// From this
this.state = {
  categories: [
    {
      category: "sports",
      ariticles: [],
      isClick: true
    },
    {
      category: "sports",
      ariticles: [],
      isClick: true
    }
  ]
};

// to this
this.state = {
  categories: {
    sports: {
      category: 'sports',
      ariticles: [],
      isClick: true
    },
    news: {
      category: 'news',
      ariticles: [],
      isClick: true
    }
  }
};

This way, you can set the state like following.

<button
  key={item.category}
  onClick={() =>
    this.setState({
      [item.category]: {
        isClick: false
      }
    })
  }
>
  {item.category}
</button>

Reply to the comment below (I've translated the comment for non-Korean speakers)

So you can't use .map any more after making such a change?

I am sorry, I should've updated the category item generation code after the state update.

No, you can't use .map as it's now an object. But thankfully, you can use either Object.keys/value/entries to iterate the this.state.categories.

You can click on "Run code snippet" button to see the output

let state = {
  categories: {
    sports: {
      category: "sports",
      ariticles: [],
      isClick: true
    },
    news: {
      category: "news",
      ariticles: [],
      isClick: true
    }
  }
};

Object.entries(state.categories).map(([id, item]) => console.log(item))

So your code would look like something like,

<div className="categoryBtn">
  {Object.entries(state.categories).map(([id, item]) => (
    <button
      key={id}
      onClick={() =>
        this.setState({
          [item.category]: {
            isClick: false
          }
        })
      }
    >
      {item.category}
    </button>
  ))}
</div>

Upvotes: 4

Mariselvam
Mariselvam

Reputation: 159

Try this :::

{this.state.categories.map(item => ( ..... ))}

Upvotes: 2

Related Questions