Rect hooks реквизит и состояние связи проблема - PullRequest
1 голос
/ 23 марта 2019

Я пытаюсь создать свое первое приложение React на основе React Hooks и с трудом выясняю, что происходит с моим состоянием / реквизитом и повторным рендерингом. Я создаю небольшую игру в догадки.

У меня есть родительский компонент GameContent, который передает своему дочернему элементу (Round) некоторые реквизиты:

  • currentRound (число)
  • roundData (объект)
  • shiftRounds (функция)

Через «shiftRounds» я говорю своему родительскому компоненту: «этот игрок дал правильный ответ», затем функция меняет раунд и счет (оба в состоянии родителей).

Хотя это кажется простым, моя функция внутри дочернего компонента, которая проверяет правильность ответа, всегда получает данные из первого раунда.

Я пробовал React.useRef, пробовал другую конфигурацию состояний, но все еще не могу найти решение.

<GameContent />

const GameContent = ({ gameOptions, gameFinish }) => {

  const [gameData, setGameData] = useState([]);
  const [round, setRound] = useState(1);
  const [score, setScore] = useState([0, 0]);

  const [ligue, dateFrom, dateTo, rounds, player1, player2] = gameOptions;

  useEffect(() => {
    //
    // Function fetches football matches. 
    //
    (async () => {
      const matchesData = await fetchMatches(ligue, dateFrom, dateTo)
      setGameData(matchesData);
    })();
  }, [])

  const nextRound = (scoringPlayer) => {
    if (round <= rounds) {
      setScore(score => {
        score[scoringPlayer] += 1;
        return score
      });
      setRound(round => round + 1);
    } else {
      finishGame()
    }
  }

  return (
    <div className="text-center">
      <h2>Round {round}</h2>
      <h3>Score</h3>
      <h2>{`${score[0]} : ${score[1]}`}</h2>
      {(gameData.length) ? <Round currentRound={round} roundData={gameData[round - 1]} shiftRounds={nextRound} players={[player1, player2]} /> : <h1>Loading...</h1>}
    </div>
  )
}

<Round />

const Round = (props) => {
  const [isCheckingAnswer, setIsCheckingAnswer] = useState(false);

  useEffect(() => {
    //
    // Set eventListeners for keydown (too capture player's answer)
    //
    document.addEventListener('keydown', (e) => { handleKeyPress(e) });
    setIsCheckingAnswer(false);
    return () => {
      document.removeEventListener('keydown', (e) => { handleKeyPress(e) });
    }
  }, [props.roundData])

  const handleKeyPress = (e) => {
    if (!isCheckingAnswer) {
      e.preventDefault();
      setIsCheckingAnswer(true);
      if (keysMap[e.key] === checkGoodAnswer(props.roundData.homeTeamScore, props.roundData.awayTeamScore)) {
        const winingPlayer = isNaN(e.key / 2) ? 0 : 1;
        props.shiftRounds(winingPlayer);
      } else {
        setIsCheckingAnswer(false);
      }
    } else {
      return
    }
  }
  return (
    <>
      <Row>
        <Col xs={4}>
          <h2>{props.roundData.homeTeam}</h2>
        </Col>
        <Col xs={4}>
          <h2>vs</h2>
        </Col>
        <Col xs={4}>
          <h2>{props.roundData.awayTeam}</h2>
        </Col>
      </Row>
      <Row className="justify-content-center my-5">
        <Col xs={4}>
          <h3>{props.players[0]}</h3>
        </Col>
        <Col xs={4}>
          <h3>{props.players[1]}</h3>
        </Col>
      </Row>
    </>
  )
}

Вспомогательные функции

const checkGoodAnswer = (homeTeamScore, awayTeamScore) => {
  if (homeTeamScore > awayTeamScore) {
    return 'homeTeam'
  } else if (homeTeamScore < awayTeamScore) {
    return 'awayTeam'
  } else {
    return 'draw'
  }
}

const keysMap = {
  'a': 'homeTeam',
  'w': 'draw',
  'd': 'awayTeam',
  '4': 'homeTeam',
  '8': 'draw',
  '6': 'awayTeam',
}

Пример выборки данных:

[
{awayTeam: "Paris Saint-Germain FC"
awayTeamScore: 2
homeTeam: "Manchester United FC"
homeTeamScore: 0},
{
awayTeam: "FC Porto"
awayTeamScore: 1
homeTeam: "AS Roma"
homeTeamScore: 2
},
...
]

Ответы [ 2 ]

1 голос
/ 23 марта 2019

Вы должны добавить ALL соответствующие переменные / функции, которые используются в вашем useEffect в качестве зависимостей.

Из документов :

... убедитесь, что массив включает в себя все значения из области действия компонента (например, реквизиты и состояние), которые меняются со временем и используются эффектом

Например, в вашем первом эффекте у вас нет зависимостей в вашем массиве:

useEffect(() => {
    //
    // Function fetches football matches. 
    //
    (async () => {
      const matchesData = await fetchMatches(ligue, dateFrom, dateTo)
      setGameData(matchesData);
    })();
  }, [])

Но вы используете ligue, dateFrom, dateTo и т.д ...

Команда реагирования предоставила хороший плагин eslint ( eslint-plugin-реагирующий-hooks ), который поможет вам решить подобные проблемы, я рекомендую попробовать его.

0 голосов
/ 29 марта 2019

Я нашел решение.Проблема была в объеме функции, вызываемой в addEventListener, я назвал ее так:

document.addEventListener('keydown', (e) => { handleKeyPress(e) });

, но правильный способ ее вызова таков:

document.addEventListener('keydown', handleKeyPress);

также Сагив указал, чтов useEffect все переменные, которые изменяются во времени и используются внутри, должны быть включены в массив, например:

 useEffect(() => {
      document.addEventListener('keydown', (e) => { handleKeyPress(e) });
      setIsCheckingAnswer(false);
      return () => {
        document.removeEventListener('keydown', (e) => { handleKeyPress(e) });
      }
    }, [props.roundData])
...