Рендеринг случайных элементов из массива в React - PullRequest
1 голос
/ 06 августа 2020

Я делаю небольшое приложение для реагирования с помощью Potter-API, через которое пользователи могут искать определенные c символы или заклинания. После получения данных из API я визуализирую 6 случайных элементов (символов / заклинаний), которые при нажатии приводят к подробному представлению элемента (символы / заклинания), я также добавил кнопку под названием randomize, которая при нажатии отображает новый набор случайных элементов.

Проблема, с которой я столкнулся, связана с этой кнопкой «рандомизировать», при многократном нажатии на нее происходит то, что вместо рендеринга только 6 элементов он начинает рендерить 7, 8, ... и прерывается в какой-то момент, приводящий к ошибке.

Я хотел бы знать, что вызывает это, и исправить это.

class RandomItems extends React.Component {

    // this.props.randomNums contain the number of random characters to display
    // and the max limit of the items (this.props.data.length) and this.props.subUrl contains
    // the detailed-view URL(characters or spells) this.props.data is an array of item objects(characters/spells) out of
    // which some characters(some = this.props.randomNums) are chosen and rendered by this component
    constructor(props) {
        super(props);
        this.state = {
            itemsList: [],
            loading: true
        }

        this.handleRandoms = this.handleRandoms.bind(this)
    }


    componentDidMount() {
        const items = this.getRandomItems()
        this.setState({itemsList: items, loading: false})
    }

    handleRandoms(){
        const items = this.getRandomItems()
        this.setState({itemsList: items})
    }

    getRandomItems() {
        function getRandomNumbers(num, limit) {
            let randoms = []
            for (let i = 0; i < num; i++) {
                randoms.push(Math.floor(Math.random() * (limit + 1)))
            }
            return randoms
        }

        const randoms = getRandomNumbers(this.props.randomNums, this.props.data.length)
        return randoms.map(value => this.props.data[value])
    }


    // Each of the returned character should be a Link to the detail view of that character
    // Using the same component for both the spells/characters page so since the object attributes
    // are different for both categories I'm using a prop accessKey that is a string(name/spell) for 
    // accessing the specific attribute based on the item type(character/spell) 
    render() {
        if (this.state.itemsList && !this.state.loading) {
            return (
                <div style={{marginTop: '6em'}}>
                    <h2>Have Some Random {(this.props.subUrl)}!</h2>
                    <br/>
                    {this.state.itemsList.map((item, index) => {
                        return (
                            <div className={'characterDesign'} key={item._id}>


                                <Link className={'highlight-link'}
                                      to={`/${this.props.subUrl}/${item._id}`}
                                >
                                    {(index + 1) + '. ' + item[this.props.accessKey]}
                                </Link>

                            </div>
                        )
                    })}
                    <button className={'fill'} onClick={this.handleRandoms}>Randomize!</button>
                </div>
            )
        } else {
            return (<h1>Loading...</h1>)
        }
    }
}

Требуемый массив объектов данных отправляется из родительского компонента

  1. После нескольких щелчков рандомизации После нескольких щелчков рандомизации
  2. После многих нажатий кнопки рандомизации После многих нажатий кнопки рандомизации

PS. Я посмотрел на массив, который отображает эти элементы, и каждый раз он содержит ровно 6 элементов (даже когда отображается большее количество элементов)

1 Ответ

0 голосов
/ 07 августа 2020

Ваша функция getRandomItems может возвращать один и тот же элемент более одного раза, поэтому, когда реакция отображает элементы, их может быть несколько с одинаковым _id (который используется как key, поэтому несколько элементов могут иметь тот же key).

Когда у вас есть несколько <div> с одним и тем же атрибутом key, response запутается. Вся суть key в том, чтобы быть уникальным. Если у вас есть несколько ключей с одним и тем же ключом, при повторном рендеринге react очищает только последний (для любого заданного ключа).

Вот минималистичный пример основной проблемы:

class RandomItems extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            itemsList: [],
            loading: true
        };
    }

    componentDidMount() {
        const items = [
          this.props.data[0],
          this.props.data[0],
          this.props.data[0]
        ];
        this.setState({
          itemsList: items
        });
    }
  
    onClickTest = () => {
      const items = [
        this.props.data[1],
        this.props.data[2]
      ];
      this.setState({
        itemsList: items
      });
    };

    render() {
        return (
          <div>
            {this.state.itemsList.map((item, index) => {
              return (
                <div key={item.id}>
                  {item.name}
                </div>
              )
            })}
            <button onClick={this.onClickTest}>Test</button>
          </div>
        )
    }
}

/////////////////////////

ReactDOM.render(
  <RandomItems randomNums={3} data={[
      {id: 0, name: 'Zeroth'},
      {id: 1, name: 'First'},
      {id: 2, name: 'Second'}
  ]}></RandomItems>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Обратите внимание, когда вы нажимаете «Проверить», что последний из трех «0 Zeroth» div удаляется (как и должно быть), а два других - нет (потому что react не ожидает нескольких div с одним и тем же key).

Лучшее решение в вашем случае, вероятно, - исправить вашу функцию randomize, чтобы она никогда не возвращала один и тот же элемент несколько раз. Пример:

getRandomItems = () => {
  let allItems = [...this.props.data];
  const randomCount = this.props.randomNums;
  
  const randomItems = [];
  for (let i = 0; i < randomCount; i++) {
    const randomIndex = Math.floor(Math.random() * allItems.length);
    const randomItem = allItems.splice(randomIndex, 1)[0];
    randomItems.push(randomItem);
  }
  
  return randomItems;
};

В качестве альтернативы вы можете изменить key с item._id на index, что также устраняет проблему, поскольку index всегда будет уникальным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...