Zander
Zander

Reputation: 1086

React toggle class on just one component

I'm creating, in componentDidMount, a lots of <div>'s.

constructor (props) {
    super(props)
    this.state = {
        componentLoaded: false,
        divs: []
    }
}

componentDidMount () {
    this.createDivs()
}

createDivs () {
    // Actually, this divs are created dinamically and with infinite scroll
    let divs = <div className='container'>
        <div className='item' onClick={() => { /* Add class */ }}>...</div>
        <div className='item' onClick={() => { /* Add class */ }}>...</div>
        <div className='item' onClick={() => { /* Add class */ }}>...</div>
        /* ... n divs ... */
    </div>
    let newDivs = this.state.divs
    newDivs.push(divs)
    this.setState({
        componentLoaded: true,
        divs: newDivs
    })
}

render () {
    return {this.state.componentLoaded ? this.state.divs : null }
    /* In my return, if X event occurs, re-call this.createDivs() to add more divs */
}

What I'm trying to achieve, is to toggle a class into only one of the .item divs, and then if clicking another one, remove it from the before and add it to the one was clicked.

I've tried to add an attribute to the state, but it didn't add it. I also searched for some solutions, but I always find solutions which doesn't toggle, as they are "toggled individually" in separated components.

Hoping to find some help, maybe this thing is real simple, but for now, I cannot figure out how to make it.

PS: I'm adding the createDivs into the state because it's an infinite scroll that re-uses the function, so I just push them into the state and the scroll won't go to the top again when adding the previous ones + the new ones.

Upvotes: 0

Views: 181

Answers (2)

T.J. Crowder
T.J. Crowder

Reputation: 1075427

First, note that you're breaking a React rule here:

this.state.divs.push(divs)

You must never directly modify state. The correct thing there is either:

this.setState({divs}); // Replaces any previous ones

or

this.setState(({divs: oldDivs}) => {divs: [...oldDivs, divs]}); // Adds to any previous ones

However, the "React way" to do this would probably be not to store those divs in state at all; instead, store the information related to them in state, and render them (in render) as needed, with the appropriate classes. The information about which one of them has the class would typically either be information on the items themselves, or some identifying information about the item (such as an id of some kind) held in your component's state.

Here's an example using items that have an id:

class Example extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            // No items yet
            items: null,
            // No selected item yet
            selectedId: null
        };
    }

    componentDidMount() {
        this.createDivs();
    }

    createDivs() {
        // Simulate ajax or whatever
        setTimeout(() => {
            const items = [
                {id: 42,  label: "First item"},
                {id: 12,  label: "Second item"},
                {id: 475, label: "third item"},
            ];
            this.setState({items});
        }, 800);
    }

    render () {
        const {items, selectedId} = this.state;
        if (!items) {
            // Not loaded yet
            return null;
        }
        return (
            <div className='container'>
                {items.map(({id, label}) => (
                    <div
                        key={id}
                        className={`item ${id === selectedId ? "selected" : ""}`}
                        onClick={() => this.setState({selectedId: id})}
                    >
                    {label}
                    </div>
                ))}
            </div>
        );
    }
}

ReactDOM.render(<Example />, document.getElementById("root"));
.selected {
    color: green;
}
.item {
    cursor: pointer;
}
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

Upvotes: 0

Malik Bagwala
Malik Bagwala

Reputation: 3029

In problems like these it is always helpful to determine what goes into react's state. You want the state to be as lightweight as possible (so you store only the stuff which is necessary)

class Test extends React.Component {
  state = {
    selectedDiv: null,
  };

  handleClick = id => {
    this.setState(prev => ({
      // sets it to null if its already active else, sets it active
      selectedDiv: prev.selectedDiv === id ? null : id,
    }));
  };
  render() {
    // Array to map over
    const divs = [1, 2, 3, 4];
    const { selectedDiv } = this.state;
    return (
      <div className="container">
        {divs.map(div => {
          return (
            <div
              key={div}
              className={selectedDiv === div ? "item class_to_add" : "item"}
              onClick={() => this.handleClick(div)}
            >Item {div}</div>
          );
        })}
      </div>
    );
  }
}

In the above examples we are only storing the unique Id of the div in the state and using that to determine if the selected div is active or not, if it is then we simply remove it from the state. The above solution does not require any complex lifecycle methods, my advice would be to keep the component as simple as possible.

PS. not part of the answer but I suggest you to look into the newer hooks API its more intuitive and most probably the future of react

Upvotes: 3

Related Questions