Состояние обновления при использовании Эффект показывает предупреждение

Я хочу обновить состояние после обновления другого состояния:

export default function App() {
  const [diceNumber, setDiceNumber] = useState(0);
  const [rolledValues, setRolledValues] = useState([
    { id: 1, total: 0 },
    { id: 2, total: 0 },
    { id: 3, total: 0 },
    { id: 4, total: 0 },
    { id: 5, total: 0 },
    { id: 6, total: 0 }
  ]);

  const rollDice = async () => {
     await startRolingSequence();
  };

const startRolingSequence = () => {
  return new Promise(resolve => {
    for (let i = 0; i < 2500; i++) {
      setTimeout(() => {
        const num = Math.ceil(Math.random() * 6);
        setDiceNumber(num);
      }, (i *= 1.1));
  }

    setTimeout(resolve, 2600);
});
};

  useEffect(()=>{
    if(!diceNumber) return;
    const valueIdx = rolledValues.findIndex(val => val.id === diceNumber);
    const newValue = rolledValues[valueIdx];
    const {total} = newValue;
    newValue.total = total + 1;

    setRolledValues([
      ...rolledValues.slice(0,valueIdx),
      newValue,
      ...rolledValues.slice(valueIdx+1)
    ])

  }, [diceNumber]);



  return (
    <div className="App">
      <button onClick={rollDice}>Roll the dice</button>
      <div> Dice Number: {diceNumber ? diceNumber : ''}</div>
    </div>
  );
}

Вот песочница.

Когда пользователь бросает кости, пара setTimeout изменяет значение состояния и в конечном итоге разрешается. Как только он разрешится, я хочу отслеживать счет в массиве объектов.

Поэтому, когда я пишу это так, он работает, но eslint выдает предупреждение об отсутствующей зависимости. Но когда я добавляю зависимость, useEffect завершается бесконечным циклом.

Как мне добиться обновления состояния после обновления состояния, не вызывая бесконечного цикла?


person Tom    schedule 10.04.2020    source источник
comment
Он конкретно жалуется на зависимость rollValues. Вы пробовали добавить ее в список зависимостей?   -  person Alexander Staroselsky    schedule 10.04.2020
comment
Когда я добавляю его, он будет обновляться при каждом обновлении, что вызовет бесконечный цикл, я обновил вопрос ссылкой на песочницу   -  person Tom    schedule 10.04.2020
comment
Это именно та зависимость, которая отсутствует в списке зависимостей. Не могли бы вы пояснить, почему вам нужно использовать useEffect для этих обновлений состояния? Можете ли вы вместо этого обновить rolledValues в rollDice?   -  person Alexander Staroselsky    schedule 10.04.2020
comment
вы правы, это сработает, однако я забыл кое-что важное, я выполняю кучу тайм-аутов, чтобы имитировать бросание игральных костей. Следовательно, мне нужно дождаться обновления состояния, пока я не смогу обновить массив для хранения очков.   -  person Tom    schedule 10.04.2020


Ответы (2)


Вот способ настроить его, который сохраняет эффект использования и не имеет проблемы с зависимостью в эффекте: https://codesandbox.io/s/priceless-keldysh-wf8cp?file=/src/App.js.

Это изменение в логике вызова setRolledValues ​​включает в себя избавление от случайной мутации в массиве rollValues, которая потенциально могла вызвать проблемы, если вы использовали его в других местах, так как все состояние React должно работать с неизменяемо, чтобы предотвратить проблемы.

Параметр setRolledValues ​​был изменен для использования параметра обратного вызова состояния для предотвращения требования зависимости.

 useEffect(() => {
    if (!diceNumber) return;

    setRolledValues(rolledValues => {
      const valueIdx = rolledValues.findIndex(val => val.id === diceNumber);
      const value = rolledValues[valueIdx];
      const { total } = value;
      return [
        ...rolledValues.slice(0, valueIdx),
        { ...value, total: total + 1 },
        ...rolledValues.slice(valueIdx + 1)
      ];
    });
  }, [diceNumber]); 

Я бы не рекомендовал работать с ним таким образом, хотя есть проблема, из-за которой, если одно и то же число выпадает несколько раз подряд, эффект срабатывает только в первый раз.

Вместо этого вы можете переместить логику в обратный вызов rollDice, что избавит от обеих возникающих проблем. https://codesandbox.io/s/stoic-visvesvaraya-402o1?file=/src/App.js

Я добавил useCallback к rollDice, чтобы гарантировать, что он не меняет ссылки, чтобы его можно было использовать в useEffects.

  const rollDice = useCallback(() => {
    const num = Math.ceil(Math.random() * 6);
    setDiceNumber(num);
    setRolledValues(rolledValues => {
      const valueIdx = rolledValues.findIndex(val => val.id === num);
      const value = rolledValues[valueIdx];
      const { total } = value;
      return [
        ...rolledValues.slice(0, valueIdx),
        // newValue,
        { ...value, total: total + 1 },
        ...rolledValues.slice(valueIdx + 1)
      ];
    });
  }, []);
person Zachary Haber    schedule 10.04.2020
comment
вы правы, это сработает, но я забыл кое-что важное, я выполняю кучу тайм-аутов, чтобы имитировать бросание игральных костей. Следовательно, мне нужно дождаться обновления состояния, пока я не смогу обновить массив для хранения очков. - person Tom; 10.04.2020
comment
Просто вызовите функцию rollDice в тайм-аутах. Это будет работать, особенно если вы используете на нем Callback. Я внесу это изменение - person Zachary Haber; 10.04.2020

У вас должна быть возможность просто вставить всю логику в setRolledValues

useEffect(() => {
  if (!diceNumber) return;

  setRolledValues((prev) => {
    const valueIdx = prev.findIndex(val => val.id === diceNumber);
    const newValue = prev[valueIdx];
    const { total } = newValue;
    newValue.total = total + 1;
    return [
      ...prev.slice(0, valueIdx),
      newValue,
      ...prev.slice(valueIdx + 1)
    ]
  })

}, [diceNumber]);

РЕДАКТИРОВАТЬ: Как отмечали другие, useEffect для этого приложения кажется неподходящим, поскольку вместо этого вы можете просто обновить setRolledValues в своей функции.

Если есть какая-то базовая система, которую нам не показывают, где вы должны использовать подобный шаблон наблюдателя, вы можете вместо этого изменить тип данных diceNumber на объект, таким образом, последующие вызовы того же число вызовет useEffect

person bilwit    schedule 10.04.2020
comment
вы правы, это сработает, но я забыл кое-что важное, я выполняю кучу тайм-аутов, чтобы имитировать бросание игральных костей. Следовательно, мне нужно дождаться обновления состояния, пока я не смогу обновить массив для хранения очков. - person Tom; 10.04.2020