1

I've got a problem with React. Let's say I have an array of items(for instance, item has id and counter and its own items). I'd like to increase the counter of any randomly selected item on any mouse click(let's say on a click of a button). How to elegantly do this, please?

Big thanks in advance!

P.S. Each item has an incremental key, and we know the number of items. But I heard that it's not a good practice to fetch an item by DOM's id. Any suggestions? Code is bellow

class Hello extends React.Component {
    render() {
        const items = [
          { id: 1, counter: 11, items: [{ id: 2, counter: 22, items: [] }] },
          { id: 3, counter: 33, items: [] },
          { id: 4, counter: 44, items: [] },
        ];
        return (
            <div>
              <Items items={items} />
              <button>Click on me to increase the counter of any random item</button>
            </div>
        );
   }
}

class Items extends React.Component {
    render() {
        return (
            <ul>
                {this.props.items.map(item => (
                    <Item key={item.id} item={item} />
                ))}
            </ul>
        );
    }
   }

class Item extends React.Component {
    render() {
        return (
            <li>
                counter={this.props.item.counter}
                <Items items={this.props.item.items} />
            </li>
        );
    }
}
  • UP! Maybe to achieve this I should have used some other js framework instead of ReactJS, please? – John Markwell Apr 26 '20 at 19:40
  • This is a trivial problem - any `JS` framework would work for this problem here, including `React`. I suggest you look into handling events - https://reactjs.org/docs/handling-events.html - and state in `React` - https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class. When you're running into issues, come back so that we can help. – goto Apr 26 '20 at 20:19
  • Thanks, goto1. Well, I think it's a bit tricky problem. It's not a problem to handle events. The problem is to retrieve randomly chosen item and update its state outside of component itself. So what would you say now? Is it possible at all? – John Markwell Apr 27 '20 at 08:18
  • You mean updating `state` of a `child` component inside the `parent` component? If so, yes, that it possible - https://reactjs.org/docs/lifting-state-up.html. – goto Apr 27 '20 at 10:11

1 Answers1

1

Hi Please check this example. Hope it helps you.

import React from "react";

export class Hello extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            items: [
                {id: 1, counter: 11, items: [{id: 2, counter: 22, items: []}]},
                {id: 2, counter: 22, items: []},
                {id: 3, counter: 33, items: []},
                {id: 4, counter: 44, items: []},
                {id: 5, counter: 55, items: []},
            ]
        }
    }

    handleClick = (e) => {
        e.preventDefault();
        let number = this.randomNumber(1, 5);
        const items = JSON.parse(JSON.stringify(this.state.items));
        items.find(item => item.id === number).counter++;
        this.setState({items: items});
    };

    randomNumber(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    render() {
        console.log('test');

        return (
            <div>
                <Items items={this.state.items}/>
                <button onClick={this.handleClick}>Click on me to increase the counter of any random item</button>
            </div>
        );
    }
}

class Items extends React.Component {
    render() {
        return (
            <ul>
                {this.props.items.map(item => (
                    <Item key={item.id} item={item}/>
                ))}
            </ul>
        );
    }
}

class Item extends React.Component {
    render() {
        return (
            <li>
                counter={this.props.item.counter}
                <Items items={this.props.item.items}/>
            </li>
        );
    }
}
Khabir
  • 5,370
  • 1
  • 21
  • 33
  • Thank you Khabir. That should work. But that is a very obvious solution and I worry about the performance. In our example, we have one level of nesting and 5 items. We will re-render all of them each time. Would it work fast if we had a number of items and levels counted by thousands, please? – John Markwell Apr 27 '20 at 08:24
  • @JohnMarkwell, not sure about your application but as per your requirement you have to update state and when you update state react will re render the component by default. There is a way to avoid re rendering using React.memo but we need to use that based on application design. – Khabir Apr 27 '20 at 09:47
  • @Khabir you should not be doing `this.state.items.find(item => item.id === number).counter++` and then `this.setState({items: this.state.items});` – goto Apr 27 '20 at 10:11
  • Hi @goto1, you suggest me, should not this but you did not tell us what should we do with requirement. – Khabir Apr 27 '20 at 10:14
  • @Khabir you **should not** be mutating state directly, which is what you're doing right now. You need to make a copy of both the `items` array and the object itself and then update the counter as well as the array - https://stackoverflow.com/a/37760774/5862900 – goto Apr 27 '20 at 10:40
  • @goto1, thanks for your suggestion. I have updated the answer. you can review it and let me know your feedback. – Khabir Apr 27 '20 at 17:51