Функция асинхронного запуска после useEffect

Мне было интересно, как можно запустить функцию после использования useEffect для извлечения данных, когда функция манипулирует данными после того, как они были извлечены?

import React, { useState, useEffect } from 'react';

const Result = (props) => {
    const [ playerName, setPlayerName ] = useState('');
    const [ playerChoice, setPlayerChoice ] = useState(null);
    const [ computerChoice, setComputerChoice ] = useState(null);
    const [ result, setResult ] = useState(null);

    useEffect(() => {
        setPlayerName(props.location.state.playerName);
        setPlayerChoice(props.location.state.playerChoice);
        setComputerChoice(generateComputerChoice);
        setResult(getResult())
    }, []);

    const getResult = () => {
        // code that runs after the setting of the playerName and playerChoice. Will return "Win", "Lose", or "Draw"

    };

    const generateComputerChoice = () => {
        const outcomes = [ 'Rock', 'Paper', 'Scissors' ];
        return outcomes[Math.floor(Math.random() * outcomes.length)];
    };

    return (
        <div className="app-container">
            <strong>YOU {result}</strong>
            <br />
            <strong>{playerName}</strong> chose <strong>{playerChoice}</strong>
            <br />
            <strong>Computer</strong> chose <strong>{computerChoice}</strong>
        </div>
    );
};

export default Result;

Итак, в этом примере я беру playerName и playerChoice с предыдущей страницы, а затем добавляю их в свой useState при загрузке страницы.

После этого я случайным образом генерирую computerChoice.

Однако после этого я хочу использовать playerChoice amd computerChoice, который был добавлен в состояние, и использовать его, чтобы увидеть, является ли игра выигрышной, проигрышной или ничьей.

result оказывается null, потому что я предполагаю, что к моменту вызова функции getResult состояния еще не установлены.

Вы, ребята, знаете, что делать в этой ситуации? Похоже, это может быть обычным делом, учитывая, что вы можете получить данные из API, а затем что-то сделать с этими данными, прежде чем захотите их отобразить.


person pyan    schedule 07.09.2019    source источник
comment
Причина, по которой у вас возникают проблемы, заключается в том, что ваш компонент делает слишком много. Вы делаете следующее: получаете данные, генерируете случайные значения, запрашиваете предыдущие состояния, сравниваете значения и отображаете результаты. Все в одном компоненте! Если бы это был я, у меня был бы компонент, который брал выбор человека и выбор компьютера и отображал результаты. У меня был бы компонент, который просто принимал данные игрока и вызывал обратный вызов. И у меня был бы общий предок с отслеживанием состояния, который обрабатывал бы выборку и рендеринг детей.   -  person Jared Smith    schedule 08.09.2019
comment
Спасибо всем за ответы. В конце концов, я переделал вариант, сгенерированный компьютером, на предыдущей странице компонентов, где игрок также делает свой выбор. Это заставляет и playerChoice, и computerChoice передаваться как свойства вместе, а это означает, что эти данные можно использовать, не беспокоясь об асинхронности. Однако мне интересно, как можно решить эту проблему. Например, если мы вызываем из api, а затем используем эти данные api для фильтрации (скажем, отсортируем их по алфавиту или что-то в этом роде), как мы будем использовать хуки для этого после вызова api useEffect?   -  person pyan    schedule 08.09.2019


Ответы (3)


Этот первый эффект не нужен. Просто делать

  const [playerName, setPlayerName] = useState(props.location.state.playerName);
person Jonas Wilms    schedule 07.09.2019
comment
Разве это не антипаттерн? Нет смысла настраивать свойства для состояния, когда компоненту не нужно изменять состояние в будущем. - person Praneeth Paruchuri; 08.09.2019

setState является асинхронным, и вы сможете использовать это состояние только в следующем рендере. В отличие от компонентов класса, хуки не позволяют выполнять обратный вызов после установки состояния.

Но если посмотреть на ваш компонент и предположить, что это будет функциональность, у вас нет причин добавлять playerName и playerChoice в состояние компонента. Вы можете использовать данные самого реквизита.

import React, { useState, useEffect } from 'react';

const Result = (props) => {
    const {playerName, playerChoice} = props.location.state;
    const [ result, setResult ] = useState(null);
    const [computerChoice, setComputerChoice] = useState(null);

    useEffect(() => {
        setComputerChoice(generateComputerChoice());
        getResult();
    }, []);

    const getResult = () => {

     // You can get the playerName, playerChoice from the props.

     // You can also setResult here.

    };

    const generateComputerChoice = () => {
        const outcomes = [ 'Rock', 'Paper', 'Scissors' ];
        return outcomes[Math.floor(Math.random() * outcomes.length)];
    };

    return (
        <div className="app-container">
            <strong>YOU {result}</strong>
            <br />
            <strong>{playerName}</strong> chose <strong>{playerChoice}</strong>
            <br />
            <strong>Computer</strong> chose <strong>{computerChoice}</strong>
        </div>
    );
};

export default Result;

Надеюсь это поможет.

person Praneeth Paruchuri    schedule 07.09.2019

Используйте ловушку useMemo и добавьте переменные состояния в его массив зависимостей. Он запомнит результат для каждого цикла рендеринга, поэтому он будет вычисляться только при обновлении playerName или playerChoice.

const getResult = useMemo(() => {
    // code that runs after the setting of the playerName and playerChoice. Will return "Win", "Lose", or "Draw"

}, [playerName, playerChoice]);

К сожалению, теперь я вижу, что вы пытаетесь сохранить это в переменной состояния result, поэтому вы можете либо использовать второй useEffect с теми же зависимостями вместо предложенного мной useMemo, либо в исходном фрагменте вместо вызова функции getResult(), которую вы изменяете подпись для getResult(name, choice) и вызов setResult с текущими значениями цикла рендеринга (прямо из реквизита).

useEffect(() => {
    const { playerName, playerChoice } = props.location.state;
    setPlayerName(playerName);
    setPlayerChoice(playerChoice);
    setComputerChoice(generateComputerChoice);
    setResult(getResult(playerName, playerChoice));
}, []);

const getResult = (name, choice) => {
    // Will return "Win", "Lose", or "Draw"
};
person Drew Reese    schedule 08.09.2019