0

I'm working with a mapped array, assigning an 'active' class to an element when it's clicked (setting activeIndex to the element's index).

But if an element's index is already the value of activeIndex when it's clicked, I want to remove the 'active' class. Currently, when I click the same element a second time, the 'active' class is not removed.

class People extends Component {
    state = {
        people: [],
        activeIndex: null,
    };

    personClickHandler = (index) => {
        this.setState({activeIndex: index})
    };

    render() {
        let people = this.state.people.map((person, index) => {
            return (
                <Person
                    name={person.name}
                    cat={person.cat}
                    key={index}
                    index={index}
                    active={this.state.activeIndex}
                    clicked={() => this.personClickHandler(index)} />
            );
        });

        return (
            <div className={classes.People}>
                {people}
            </div>
        );
    }
}

What I've tried:

I know I need to do some sort of state comparison. My initial thought was to compare the current value of activeIndex with prevState.activeIndex inside the click event handler, but I'm still running into the same issue, where the class never gets removed if the element is clicked a second time.

personClickHandler = (index) => {
    this.setState(prevState => ({
        activeIndex: (prevState.index !== index) ? index : null,
    }));

    this.setState({activeIndex: index})
};

What is the best approach for this?

asw1984
  • 713
  • 1
  • 7
  • 17
  • `active={this.state.activeIndex === index}`, then in your Person component, if active is not false, it will add the class name otherwise it wont: className=`{active ? 'active' : ''}`. Also take a look at this: https://github.com/JedWatson/classnames it will make your code cleaner too – Luciano Semerini Apr 13 '19 at 01:10
  • 1
    @LucianoSemerini It would be simpler to write `className={active && 'active'}` since you wouldn't be assigning a value if not active. – Adam Apr 13 '19 at 02:20
  • Indeed. Classnames package is quite nice too to write this nice and clean. Specially if you later have to add multiple conditional class names – Luciano Semerini Apr 13 '19 at 04:36

1 Answers1

0

First you need a way to dynamically determine if the current Person in the map is the active one. To do that, simply equate the value of this.state.activeIndex to the current map index with this.state.activeIndex === index.

If you do this, you won't need any special logic in the click function's setState. Just keep it exactly as you have it in your first code sample.

Now that each Person will know if it's active or not, you can use short-circuit logic to add the active class to the clicked Person with className={active && 'active'}.

// People.jsx
personClickHandler = (index) => {
  this.setState({ activeIndex: index }) // No need to involve previous state
}

render() {
  let people = this.state.people.map((person, index) => {
    return (
      <Person
        name={person.name}
        key={index}
        active={index === this.state.activeIndex} // Pass boolean for each Person
        clicked={() => this.personClickHandler(index)} />
    )
  })
  ...
}
// Person.jsx
render() {
  let active = this.props.active

  return (
    <div
      className={active && 'active'} // Short-circuit logic
      onClick={this.props.clicked}>
      {this.props.name}
    </div>
  )
}

Here's a live demo of this code.

Adam
  • 3,829
  • 1
  • 21
  • 31