Search code examples
reactjstsx

Reactjs - toggling class indipendently with right way


I am trying to add a class and remove it it's available already. I am doing to for each element separately. but it always refers the last element. what is the correct way to do this?

is there any easy and elegant way to do this?

here is my component:

import React from "react";
import "./style.css";

export default class App extends React.Component {
  divRef;
  constructor(props){
    super(props)
    this.divRef = React.createRef();
  }
  toggleView = (e) => {
        e.preventDefault();
        if(this.divRef.current.classList.contains("active")){
          this.divRef.current.classList.remove("active"); //always refer div 2!?
          return;
        }
        this.divRef.current.classList.add("active");
    }
  render(){
     return (
    <div class="parent">
      <div ref={this.divRef}>
      1
      <a href="#" onClick={(e) => this.toggleView(e)}>toggle</a>
      </div>
      <div ref={this.divRef}>2
      <a href="#" onClick={(e) => this.toggleView(e)}>toggle</a>
      </div>
    </div>
  );
  }
}

Live demo


Solution

  • I would utilize state to achieve this. No need for refs.

    import React from "react";
    import "./style.css";
    
    export default class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          view1: false,
          view2: false
        };
      }
      toggleView = (e, view) => {
        e.preventDefault();
        this.setState({ [view]: !this.state[view] });
      };
      render() {
        return (
          <div class="parent">
            <div className={this.state.view1 ? "active" : ""}>
              1
              <a href="#" onClick={e => this.toggleView(e, "view1")}>
                toggle
              </a>
            </div>
            <div className={this.state.view2 ? "active" : ""}>
              2
              <a href="#" onClick={e => this.toggleView(e, "view2")}>
                toggle
              </a>
            </div>
          </div>
        );
      }
    }
    

    Live Demo

    EDIT: Here I show how this approach scales when we have lots of items

    import React from "react";
    import "./style.css";
    
    const NUM_ITEMS = 20;
    const items = Array.from({ length: NUM_ITEMS }).map((_, idx) => ({
      title: "title" + idx,
      active: false
    }));
    
    const Item = ({ active, title, onToggle }) => (
      <div className={active ? "active" : ""}>
        {title}
        <a
          href="#"
          onClick={e => {
            e.preventDefault();
            onToggle();
          }}
        >
          toggle
        </a>
      </div>
    );
    
    export default class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          items
        };
      }
      toggleView = idx => {
        const items = [...this.state.items];
        items[idx] = { ...items[idx], active: !items[idx].active };
        this.setState({ items });
      };
      render() {
        return (
          <div class="parent">
            {this.state.items.map((item, idx) => (
              <Item
                key={item.title}
                active={item.active}
                title={item.title}
                onToggle={() => this.toggleView(idx)}
              />
            ))}
          </div>
        );
      }
    }
    

    Demo