0

I need to track the set of items a user selects. I initialized the component's state to contain an array to hold the set of data-ids the user clicks.

Each item contains an "onClick" listener and a data-id attribute with a value:

<div onClick={this.selectItem} data-id={this.props.id}>Select this item</div>

My class and method:

class Item extends React.Component {
  constructor() {
    super();

    this.selectItem = this.selectItem.bind(this);

    this.state = {
      itemsSelected: []
    };
  }

  selectItem(e) {
    const id = e.target.dataset.id;

    // copy state to new array with the new value
    let itemsSelected = [...this.state.itemsSelected, id];

    // Set state and display it, after update occurred
    this.setState({ itemsSelected }, function() {
      console.log('itemsSelected: ', this.state.itemsSelected);
    }); 
  }

I would expect the console log to display a growing set of ids as items are clicked, however it displays only the last item clicked (array length is always 1). YET, if the same item is clicked, the array grows.

So, clicking item 1, then 2, then 3 results in the state being set with an array of one value of 3. And clicking item 1 three times results in an array of 3 values, each being the value of 1.

I want the array to contain the ids of any/all items the user clicks.

Project dependencies:

"dependencies": { "history": "4.2.0", "re-base": "2.2.0", "react": "15.3.2", "react-addons-css-transition-group": "15.3.2", "react-dom": "15.3.2", "react-router": "4.0.0-alpha.4" },

2 Answers 2

1

Looks like each of your Items handles its own clicks and stores its own itemsSelected array inside its own state. What you have to do is to introduce some sort of ItemsContainer which will subscribe to Item's clicks and will have this way one itemsSelected which in turn will collect all selections.

UPDATE:

Ok, here is detailed explanation. Each React Component have its own state. When you call this.setState inside component only its state will be updated:

              +---------------------+
              |       Item          |
    updates   |                     |
 +----------> |state: { ... }       |
 |            |                     |   calls
 +----------+ |this.setState({ ... }| <----------+
              |                     |            |
              |this.selectItem  +----------------+
              |                     |
              +---------------------+

When you renders several Item Components (and looks like you do) each of them will have its own state. Let's say you renders two Items with ids 1 and 2. Initially they both will have empty itemsSelected arrays as their state:

 +------------------------+    +------------------------+
 |       Item (id = 1)    |    |       Item (id = 2)    |
 |                        |    |                        |
 | state: {               |    | state: {               |
 |   itemsSelected: []    |    |   itemsSelected: []    |
 | }                      |    | }                      |
 +------------------------+    +------------------------+

Each of them will have it's own selectItem handler updating its own state. After you'll click first one 2 times and click second once their states will be:

 +------------------------+    +------------------------+
 |       Item (id = 1)    |    |       Item (id = 2)    |
 |                        |    |                        |
 | state: {               |    | state: {               |
 |   itemsSelected: [1, 1]|    |   itemsSelected: [2]   |
 | }                      |    | }                      |
 +------------------------+    +------------------------+

And that is exactly what you see in your console. What you have to do is to introduce another ItemContainer which will render all the items and listen to their selections. To subscribe to Item's selection it must pass callback function inside Items' props. Items in turn will invoke that callback inside their selectItem handlers.

+---------------------+                      +---------------------+
|       Item          |                      |    ItemContainer    |
|                     |       pass onSelect  |                     |
|                     | <--------------------+this.render          |
|   this.selectItem   |                      |                     |  calls
|         +           |                      |this.setState({ ... }| <-------+
|         |           | this.props.onSelect  |                     |         |
|         +--------------------------------> |this.onSelect +----------------+
+---------------------+                      +---------------------+
Sign up to request clarification or add additional context in comments.

4 Comments

hmm... so an element can have its own state? So how then do I access the component's state? I thought I was doing that. I need to update the itemsSelected property of the component's state. Sorry I'm not following your response. I'm very new to React.
@MarkSalvatore elaborated a bit on my explanation.
Oh I see. Maybe I just need to put selectItem() in the parent component where it can manage the Item child component. I probably just need to pass the method down to <Item> and things will behave as hoped. Does that sound right?
@MarkSalvatore yeah, exactly what I tried to explain.
0

It's because setState is asynchronous. You can't rely on the state from the setState :

https://facebook.github.io/react/docs/react-component.html#setstate

3 Comments

Right, and that's why I included the callback function in setState(). I believe that callback should ensure seeing state only AFTER it updates.
I think it's because React tries to group up similar setState to minimize calls. In your example, you are doing nothing with setState ? a this.setState({ itemsSelected }, function() { return { itemsSelected }; }); could do the thing
But I do call setState(). As I understand it, setState() can be passed a callback function when the state gets set, whereby I chose to console.log the updated state.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.