0

I have an API endpoint that returns 20 items. I make a call and store these items as an array called my "items" on the state. I want to show 4 items at a time and then when a user clicks on the particular node it will remove said item from the list being displayed and replace it with another item from the array that hasn't been used.

The tricky part I'm facing is referencing the exact location that the item has been removed from. For example, if from my 4 items I click on item 2 to be removed I want to randomly select an item that hasn't been used and add it in the location that item two was, rather than sliding everything up and it becoming item 4.

How would I go about doing this? thanks for you help.

----- ANSWER -------

Thanks everyone and in-particular @wdm for sending me in the right direction. It answered a large chunk of my question, however it looped back through the results. I wanted to select items that haven't been used.

I added a little more functionality to the state in order to track the items being displayed and the items already used.

note: I've tweaked the "replaceItem" method as the while loop had a memory leak once it reached the end of the list. The else statement runs after we've already shown every one of the items and thus can go ahead and start to remove them from being shown to the user.

state = {
        companies: [
            { id: 1, name: "Chris" },
            { id: 2, name: "Ringo" },
            { id: 3, name: "John" },
            { id: 4, name: "Marty" },
            { id: 5, name: "Beetlejuice" },
            { id: 6, name: "Dwayne" },
            { id: 7, name: "Spud" },
            { id: 8, name: "Ant" },
            { id: 9, name: "Spaghetti" },
            { id: 10, name: "Meatballs" }
        ],
        items: [],
        usedItems: []
    };

    componentDidMount() {
        this.generateList();
    }

    generateList = () => {
        const { companies } = this.state;
        let items = Object.keys(companies.slice(0, 4));

        this.setState({
            items,
            usedItems: items
        });
    };

    replaceItem = i => {
        const { companies, items, usedItems } = this.state;
        let newItem = null;

        if (companies.length !== usedItems.length) {
            while (newItem === null) {
                let newCount = Math.floor(
                    Math.random() * companies.length
                ).toString();

                if (usedItems.indexOf(newCount) === -1) {
                    newItem = newCount;
                }
            }

            let newItems = [...items];
            newItems[i] = newItem;
            this.setState(prevState => {
                return {
                    items: newItems,
                    usedItems: [...prevState.usedItems, newItem]
                };
            });
        } else {
            this.setState(prevState => {
                return {
                    items: prevState.items.filter(
                        item => items.indexOf(item) !== i
                    )
                };
            });
        }
    };
  • Could you please include the code you have written so far? – Tholle Jul 23 '18 at 20:57
  • You'll need to keep track of which ones are displayed and which ones are not. When the user clicks on an item to remove, remove it from the list, get the indexes of all of the item not displayed, [get a random value from that array](https://stackoverflow.com/q/4550505), and set that item to be displayed. – Heretic Monkey Jul 23 '18 at 21:06

4 Answers4

1
  1. Store a separate array in your state which contains the indexes of the displayed items, such as displayedItems: [2, 5, 11, 9].
  2. Set an onClick event for the nodes. This event should select an index from items that doesn't match any indexes in displayedItems and then replace the respective index in displayedItems with this new index.
  3. This should trigger a re-render since the state changed and your list should be updated.

Live Demo: https://codesandbox.io/embed/1y92qn9pq3?hidenavigation=1

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            items: ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta"],
            selectedItems: []
        };
    }
    componentDidMount() {
        this.populateList();
    }
    populateList = () => {
        const { items } = this.state;
        let selectedItems = [];
        while (selectedItems.length < 4) {
            const item = Math.floor(Math.random() * items.length);
            if (selectedItems.indexOf(item) === -1) {
                selectedItems.push(item);
            }
        }
        this.setState({ selectedItems });
    };
    replaceItem = key => {
        const { items, selectedItems } = this.state;
        let newItem = null;
        while (newItem === null) {
            const item = Math.floor(Math.random() * items.length);
            if (selectedItems.indexOf(item) === -1) {
                newItem = item;
            }
        }
        let newSelectedItems = [...selectedItems];
        newSelectedItems[key] = newItem;
        this.setState({ selectedItems: newSelectedItems });
    };
    render() {
        return (
            <div className="App">
                <ul>
                    {this.state.selectedItems.map((val, key) => (
                        <li
                            className="item"
                            key={key}
                            onClick={() => this.replaceItem(key)}
                            >
                                {this.state.items[val]}
                            </li>
                        ))}
                    </ul>
                </div>
            );
        }
    }
wdm
  • 7,121
  • 1
  • 27
  • 29
0

whan you map over 4 number of elements to show them, assign them an index, and an onClick listener that sends that index to handler, so you know which index should be replaced

{
  this.state.selctedItems.map((item, index) => (
    //for example
    <p onClick={() => this.handler(index)}> {item.content} </p>
  ))
}
Kavian Rabbani
  • 934
  • 5
  • 15
0

You could keep a list of the (potentially) four items you display at any given moment. As items are clicked, you can pull from the data array, setting the element to the spot you want displayed

   render() {
      return <DispList list={ilist}/>
   }

So the "display-list" component takes the list-array in as a property. It maintains 4-element array to contain the currently displayed items and an index to the next element to be added. Generating the next state is done in getDerivedStateFromProps, filling in any empty display-array elements with the next from the list. When a displayed element is clicked, it deletes entry from the display-list, where it can be re-filled in the next getDerivedStateFromProps invocation.

class DispList extends Component {
  constructor() {
    super()
    this.state = {
      display: new Array(4),
      count: 0
    }
    this.click = this.click.bind(this)
  }
  click(e,i) {
    this.setState((s) => (delete s.display[i], s))
  }

  static getDerivedStateFromProps(props, state) {
    for (let i=0; i < state.display.length; i++)
      if (typeof state.display[i] === 'undefined')
        state.display[i] = ilist[state.count++];
    return state 
  }

  render() {
    return (
      this.state.display.map((item, i) => <div key={i} onClick={e => this.click(e,i)}>{item}</div>)
    )
  }
}
bill.lee
  • 2,207
  • 1
  • 20
  • 26
0

Here's a possible logic:

const Beatle = ({ name, onClick}) => (
  <div onClick={() => onClick(name)}>
    {name}
  </div>
);

class Beatles extends Component {
  state = {
    beatles: ['John', 'Paul', 'Ringo', 'George'],
  };

  handleClick = (beatleToDelete) =>
    this.setState(prevState => (
      { beatles: prevState.beatles.filter(beatle => beatle !== beatleToDelete)}
  ));

 render() {
  return (
    <div className="App">
      {this.state.beatles.map((beatle) => (
        <Beatles
          key={beatle}
          name={beatle}
          onClick={this.handleClick}
        />
      ))}
    </div>
  );
 }
}
soupette
  • 1,260
  • 11
  • 11