Почему мое событие onChange для ввода изменяет несколько значений состояния в массиве объектов? - PullRequest
1 голос
/ 25 сентября 2019

Я создаю поток пользователей, чтобы позволить пользователям вводить статистику для видеоигры, в которую они играли.Мой компонент реагирования отображает таблицу, подобную Excel, где они могут вручную вводить статистику (например, электронную таблицу Excel, где ось Y - это столбец игроков, а ось X - статистика для каждого игрока.

КогдаЯ обновляю конкретную статистику для конкретного игрока, также изменяется значение статистики каждого игрока для этой конкретной характеристики. Например, если я обновлю Игроку A 5 головами, у каждого другого игрока в команде также будет 5 голов. Этого не должно быть.

Сначала я инициализирую объект matchStats:

initializeMatchStats = (numGames) => {
    const {matchInfo} = this.props;
    const gameArray = new Array(numGames).fill('');

    const matchStats = gameArray.map((game, i) => {
        let statsWithKeys = {};
        let gameInfo = {};

        matchInfo.circuitStats.map(key => {
            statsWithKeys[key.toLowerCase()] = 0
        })

            const players = new Array(matchInfo.playersAllowedInGame).fill({
                stats: statsWithKeys
            }).map(p => {
                return {
                    ...p,
                    dropdownOpen: false,
                    id: Math.random().toString(36)
                }
            })

            gameInfo = {
                players,
                index: i + 1
            }

        return gameInfo
    })
    this.setState({matchStats, numGames, numGamesModalOpen: false, uploadManually: true}, () => console.log('matchStats: ', matchStats))
}

Затем я обновляю объект matchStats по мере его изменения пользователем:

updateStatsManually = (val, player, game, header, e) => {

        e.preventDefault();

        const {matchStats} = this.state;
        // matchStats is an array of games in each match. A game is made up of the players (and their respective stats) in the game.
        const { matchInfo } = this.props;

        const gameToUpdate = matchStats.find(g => g.index === game.index)

        let playerToUpdate = gameToUpdate.players.find(p => p.id === player.id)

        let newStats = playerToUpdate.stats;
        newStats[header] = val;
        playerToUpdate.stats = newStats;
        gameToUpdate.players = [...gameToUpdate.players.filter(p => p.id !== player.id), playerToUpdate]

        this.setState({
            matchStats: [...matchStats.filter(g => g.index !== game.index), gameToUpdate]
        })
    }

Здесьблок кода реагирования:

<Section>
    {matchStats.find(g => g.index === game).players.map((player, i) => <Row key={i}>
        {!uploadManually && <Column>
            <Input disabled style={{textAlign: 'center'}} placeholder={player.name} />
        </Column>}
        {circuitStats.map((header, i) => <Column key={`${i}-${player.id}`}>
            <Input id={`${i}-${player.id}-${header}`} value={player.stats[header.toLowerCase()]} disabled={!uploadManually} onChange={(e) => updateStatsManually(e.target.value, player, currentGame, header.toLowerCase())} />
        </Column>)}
    </Row>)}
</Section>

Я ожидал бы, что когда я изменяю входное значение для одного стата для данного игрока, он изменяет только значение стата для этого конкретного игрока. Однако он меняет его для каждого игрока.игрок.

Я уже некоторое время возился с этим и пытаюсь понять, что я делаю неправильно. Я подумал, что это может быть связано с тем, как поступаютотображается в функции .map, но я попытался разделить входные данные теста, и результат был таким же.Любая помощь приветствуется!

Ответы [ 2 ]

2 голосов
/ 26 сентября 2019

Первая проблема заключается в следующей строке:

const players = new Array(matchInfo.playersAllowedInGame).fill({
    stats: statsWithKeys
}) // ... etc ...

При передаче объекта в Array#fill каждый элемент заполняется ссылкой на этот единственный объект.Фрагмент стека ясно показывает это в следующем минимальном представлении:

const arr = Array(4).fill({age: 11});
console.log(JSON.stringify(arr));
arr[0].age = 42;
console.log(JSON.stringify(arr));
console.log(arr);

Измените строку на

const players = new Array(matchInfo.playersAllowedInGame).fill().map(e => ({
  stats: statsWithKeys
})) // ... etc ...

, то есть верните отдельный объект для каждого индекса в массиве, к которому следует обратиться.Или, поскольку вы связываете это с другим вызовом .map, вы можете объединить их в один.

Вот исправление к вышеприведенному представлению:

const arr = Array(4).fill().map(e => ({age: 11}));
console.log(JSON.stringify(arr));
arr[0].age = 42;
console.log(JSON.stringify(arr));
console.log(arr);

Вторая проблема (похожая на первую) состоит в том, что существует объект let statsWithKeys = {};, который псевдоним для каждого player с помощью вышеуказанной функции map.Нам нужен отдельный экземпляр statsWithKeys для каждого player.

Полная перезапись, которая перемещает инициализацию statsWithKeys в map.Я использовал некоторые фиктивные данные, чтобы сделать их воспроизводимыми, и исключил ненужные временные переменные:

const numGames = 3;
const matchInfo = {
  circuitStats: ["foo", "bar"], 
  playersAllowedInGame: 4
};

const matchStats = Array(numGames).fill().map((game, i) => {
  return {
    index: i + 1,
    players: Array(matchInfo.playersAllowedInGame).fill().map(p => {
      return {
        stats: matchInfo.circuitStats.reduce((a, e) => {
          a[e.toLowerCase()] = 0;
          return a;
        }, {}),
        dropdownOpen: false,
        id: Math.random().toString(36) // this is not actually safe! use a uuid
      }
    })
  }
});

matchStats[0].players[0].stats.foo = 42; // try setting something
console.log(matchStats);

Другая важная проблема, как указано выше, заключается в том, что Math#random небезопасно для генерации уникальных идентификаторов .Если у вас много данных (или даже если у вас их нет, а код выполняется достаточно часто), вы столкнетесь с непреднамеренным поведением.См. Ссылки в этом параграфе для получения последних исправлений.

1 голос
/ 26 сентября 2019

Допустим, у вас есть лимит в 5 игроков.В данном случае это:

         const players = new Array(matchInfo.playersAllowedInGame).fill().map(p => {
                return {
                   stats: statsWithKeys,
                   dropdownOpen: false,
                   id: Math.random().toString(36)
                }
            })

не создает 5 'statsWithKeys', как вы ожидаете, а скорее 5 ссылок на тот же 'statsWithKeys'.

Лучший способисправить это - использовать оператор распространения непосредственно на самом объекте:

         const players = new Array(matchInfo.playersAllowedInGame).fill().map(p => {
                return {
                   stats: { ...statsWithKeys },
                   dropdownOpen: false,
                   id: Math.random().toString(36)
                }
            });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...