6

I have a bootstrap grid where each grid item is populated from an array of objects but after each grid item I would like to have a vote button. How could I achieve this with maintaining state on each button separately, ie when button 1 is clicked the text should change from 'vote' to 'voted' whilst the others remain as 'vote'.

At the moment when a button is clicked, all of them change to 'Voted'

class Items extends Component {
    constructor(props) {
        super(props);
        this.state = { hasVoted: false };

        this.OnClick = this.OnClick.bind(this);
    }

    OnClick() {
        this.setState(prevState => ({
            hasVoted: !prevState.hasVoted
        }));
    }

    render() {
        const Item = teasers.items.map(item =>
            <Col key={item.nid}>
                <span>
                    {itemType}
                </span>

                <a href={item.path}>
                    <Image src={item.image.src} title={item.productType} />
                    <span>
                        {item.Title}
                    </span>
                    <div className={teasersStyle.copy}>
                        {" "}{item.Copy}>
                    </div>
                </a>

                <div
                    className={this.state.hasVoted ? "active" : "notactive"}
                    onClick={this.OnClick}
                >
                    {this.state.hasVoted ? "Voted" : "Vote"}
                </div>
            </Col>
        );
        return (
            <div>
                <Grid>
                    <Row>
                        {Item}
                    </Row>
                </Grid>
            </div>
        );
    }
}

export default Items;
3
  • Make hasVoted a property of each item. Commented Jul 31, 2017 at 16:03
  • @IngoBürk Not sure I understand exactly what you mean. Like this: hasVoted = { this.state.hasVoted } Commented Jul 31, 2017 at 16:10
  • You can either make your buttons stateful or you can store their hasVoted state in Items state as an array of booleans with an index that matches the button's index within teasers.items. Then when you map teasers.items.map((item, index) =>{...}), you can use the index to access the hasVoted state and apply the property to the button. As an aside: You're using this.handleOnClick but your method is named OnClick(). Commented Jul 31, 2017 at 16:10

1 Answer 1

17

I have created a simple example for you:

class App extends React.Component {
    constructor() {
        super();
        this.onClick = this.onClick.bind(this);
        this.state = {
            arr: [
                { name: "first", isActive: true },
                { name: "second", isActive: true },
                { name: "third", isActive: true },
                { name: "fourth", isActive: true }
            ]
        };
    }
    onClick(index) {
        let tmp = this.state.arr;
        tmp[index].isActive = !tmp[index].isActive;
        this.setState({ arr: tmp });
    }
    render() {
        return (
            <div>
                {this.state.arr.map((el, index) =>
                    <div key={index} onClick={() => this.onClick(index)}>
                        name: {el.name} / isActive: {el.isActive ? "true" : "false"}
                    </div>
                )}
            </div>
        );
    }
}

Check the fiddle and implement it in your case.

One more way to handle this is keeping the index of an active button in the state:

class App extends React.Component {

state = {
    users: [
    { name: "John" },
    { name: "Sarah" },
    { name: "Siri" },
    { name: "Jim" },
    { name: "Simon" },
  ],
  activeIndex: 0,
}

render() {
    const { users, activeIndex } = this.state;

    return (
      <div>
        {users.map((u, i) => (
          <div
            className={i === activeIndex ? 'active' : ''}
            onClick={() => this.setState({ activeIndex: i })}
            key={u.name}
          >
            {u.name}
          </div>
        ))}
      </div>
    )
  }
}

https://jsfiddle.net/846tfe3u/

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks Andrew, works great but is there an easy way to make all others false when one is clicked?
Thanks a lot Andrew
Is there a way applying this solution for a dynamic number of components?
@guygrinberger you can keep in the state an index of an active button: buttons.map((button, index) => <Button active={this.state.activeIndex === index} onPress={() => this.setState({ activeIndex: index })} name={button.name} />) I wrote this just from my head, so it's just a direction :)

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.