反应useState不更新值

Posted

技术标签:

【中文标题】反应useState不更新值【英文标题】:react useState not updating value 【发布时间】:2020-10-28 14:38:27 【问题描述】:

我正在使用 react typescript,我使用 useState 更新值,但该值没有在函数中更新,我使用了 const [isGameStarted, setIsGameStarted] = React.useState<any>('0');,并且我正在更新它在 useEffect() 函数中的值,

React.useEffect(() => 
        if(gameData?.game?.roundStarted) 
            if(isGameStarted == '0') 
                console.log("round is started");
                setIsGameStarted('1');
            
        
    , [gameData]);

这里我已将其值更新为 1,但对于我的 interval 函数,它没有更新该值,这里我提到了我的 interval 函数,此间隔函数每 1 秒调用一次,但它始终考虑 @987654330 @ value 为 0,任何人都可以帮助我为什么即使在 useEffect() 函数调用之后它没有得到 value 为 1,任何帮助都将不胜感激

const interval = () => 
        let timer = setSeconds, minutes, seconds;
        
        console.log("isGameStarted : "+isGameStarted);

        if(isGameStarted == '0') 
            alert("0")
         else 
            alert("1")
        
       

完整代码:

import  Alert  from "@material-ui/lab";
import  Typography, useMediaQuery  from "@material-ui/core";
import  ShowWinner  from "./ShowWinner";
import  ErrorBoundary  from "../../../../App/ErrorBoundary";
import  GamePlayWhite  from "../../GamePlayWhite";
import  GamePlayBlack  from "../../GamePlayBlack";
import  GamePlaySpectate  from "../../GamePlaySpectate";
import React,  useEffect, useState  from "react";
import  useDataStore  from "../../../../Global/Utils/HookUtils";
import  GameDataStore  from "../../../../Global/DataStore/GameDataStore";
import  UserDataStore  from "../../../../Global/DataStore/UserDataStore";
import  IntervalDataStore  from "../../../../Global/DataStore/IntervalDataStore";
import GameStart from "../../GameStart";
import GameJoin from "../../GameJoin";
import moment from "moment";
import  ChatDataStore  from "../../../../Global/DataStore/ChatDataStore";
import  useHistory, useParams  from "react-router";
import  SiteRoutes  from "../../../../Global/Routes/Routes";
import  getTrueRoundsToWin  from "../../../../Global/Utils/GameUtils";
import  ClientGameItem  from "../../../../Global/Platform/Contract";
import  CurriedFunction1  from "lodash";



interface Props 
    gameId: string;



export const GameInner: React.FC<Props> = (
    
        gameId,
    


) => 
    
    const gameData = useDataStore(GameDataStore);
    const userData = useDataStore(UserDataStore);
    const chatData = useDataStore(ChatDataStore);
    const params = useParams< throwaway?: string >();
    const history = useHistory();
    const [updateShowTimer, setUpdateShowTimer] = React.useState('02:00');
    const [isCalled, setIsCalled] = React.useState<any>('0');
    const [intervalData, setIntervalData] = useState(null as NodeJS.Timeout | null);
    const [isGameStarted, setIsGameStarted] = React.useState<any>('0');
    let setSeconds = 30;

    const 
        dateCreated,
        started,
        chooserGuid,
        ownerGuid,
        spectators,
        pendingPlayers,
        players,
        settings,
        kickedPlayers
     = gameData.game ?? ;

    const 
        playerGuid
     = userData;

    const iWasKicked = !!kickedPlayers?.[playerGuid];
    const amInGame = playerGuid in (players ?? );

    useEffect(() => 
        const playMode = params.throwaway !== "play" && started && !iWasKicked && amInGame;
        const notPlayMode = iWasKicked && params.throwaway === "play";
        if (playMode) 
            history.push(SiteRoutes.Game.resolve(
                id: gameId,
                throwaway: "play"
            ))
        

        if (notPlayMode) 
            history.push(SiteRoutes.Game.resolve(
                id: gameId,
                throwaway: "kicked"
            ));
        
        getUpdate();
    , [started, iWasKicked, amInGame]);

    React.useEffect(() => 
        if(gameData?.game?.roundStarted) 
            if(isGameStarted == '0') 
                console.log("round is started");
                setIsGameStarted('1');
            
        
    , [gameData]);

    

    const skipPlayer = (game_string_id: any, target_turn: any, chooserGuid: any) => 
        return GameDataStore.skipPlayer(game_string_id, target_turn, chooserGuid);
    

    const interval = () => 
        let timer = setSeconds, minutes, seconds;

        let chooserGuid = localStorage.getItem('chooserGuid');
        let game_string_id = localStorage.getItem('game_id');
        let target_turn = localStorage.getItem('target_turn');
        let is_called = localStorage.getItem('is_called');
        
        console.log("isGameStarted : "+isGameStarted);

        if(isGameStarted == '0') 
            if (typeof timer !== undefined && timer != null) 
                minutes = parseInt(timer / 60 as any, 10);
                seconds = parseInt(timer % 60 as any, 10);
                minutes = minutes < 10 ? "0" + minutes : minutes;
                seconds = seconds < 10 ? "0" + seconds : seconds;

                //console.log("test");
                console.log(minutes + ":" + seconds);

                setUpdateShowTimer(minutes+":"+seconds);

                if (timer == 0) 
                    skipPlayer(game_string_id, target_turn, chooserGuid);
                    if(intervalData != undefined && intervalData!== null)
                    clearInterval(intervalData);
                

                if (--timer < 0) 
                    if(intervalData != undefined && intervalData!== null)
                    clearInterval(intervalData);
                
                setSeconds -= 1;
            
        
       

    const startTimer = () => 
        console.log("called again");
        //interval_counter = setInterval(interval,1000);
        setIntervalData(setInterval(interval,1000));
    

    const getUpdate = () => 
        if(gameData?.game?.players && gameData?.game?.id) 
            let game_id = gameData.game.id;
            let all_players = gameData.game.players;
            let all_player_id = Object.keys(all_players);
            let filteredAry = all_player_id.filter(e => e !== userData.playerGuid);
    
            console.log("user player guid:"+userData.playerGuid);
            console.log("guid:"+chooserGuid);   
            console.log("all players:"+all_player_id);  
            console.log("new array:"+filteredAry);
    
            let target_item = filteredAry.find((_, i, ar) => Math.random() < 1 / (ar.length - i));
            if(typeof target_item !== undefined && target_item!=null) 
                localStorage.setItem('target_turn',target_item);
            
    
            localStorage.setItem('is_started','0');
            if(typeof game_id !== undefined && game_id!=null) 
                localStorage.setItem('game_id',game_id);
            
            if(typeof chooserGuid !== undefined && chooserGuid!=null) 
                localStorage.setItem('chooserGuid',chooserGuid);
            
            if(isChooser) 
                if(isCalled == '0') 
                    setIsCalled("1");
                    startTimer();
                
             else 
                //clearInterval(intervalData);
            
        
    

    const isOwner = ownerGuid === userData.playerGuid;
    const isChooser = playerGuid === chooserGuid;
    const amSpectating = playerGuid in  ...(spectators ?? ), ...(pendingPlayers ?? ) ;

    const playerGuids = Object.keys(players ?? );
    const roundsToWin = getTrueRoundsToWin(gameData.game as ClientGameItem);
    const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin);

    const inviteLink = (settings?.inviteLink?.length ?? 0) > 25
        ? `$settings?.inviteLink?.substr(0, 25)...`
        : settings?.inviteLink;

    const meKicked = kickedPlayers?.[playerGuid];

    const tablet = useMediaQuery('(max-width:1200px)');
    const canChat = (amInGame || amSpectating) && moment(dateCreated).isAfter(moment(new Date(1589260798170)));
    const chatBarExpanded = chatData.sidebarOpen && !tablet && canChat;


    /**********************************************/
    
    /********************************************/

    

    return (
        <div style= maxWidth: chatBarExpanded ? "calc(100% - 320px)" : "100%" >
            <div style= minHeight: "70vh" >
                iWasKicked && (
                    <Alert variant="filled" severity="error">
                        <Typography>
                            meKicked?.kickedForTimeout ? "You were kicked for being idle. You may rejoin this game any time!" : "You left or were kicked from this game"
                        </Typography>
                    </Alert>
                )
                !winnerGuid && settings?.inviteLink && (
                    <Typography variant="caption">
                        Chat/Video Invite: <a href=settings.inviteLink target="_blank" rel="nofollow noreferrer">inviteLink</a>
                    </Typography>
                )
                winnerGuid && (
                    <ShowWinner />
                )
                !winnerGuid && (
                    <ErrorBoundary>
                        updateShowTimer isGameStarted
                        (!started || !(amInGame || amSpectating)) && (
                            <BeforeGame gameId=gameId isOwner=isOwner />
                        )  
                        

                        started && amInGame && !isChooser && ( 
                            [
                                <GamePlayWhite />
                            ]
                        ) 

                        started && amInGame && isChooser && (
                            [
                                <GamePlayBlack />
                            ]
                        )

                        started && amSpectating && (
                            <GamePlaySpectate />
                        )
                    </ErrorBoundary>
                )
            </div>
        </div>
    );
;

【问题讨论】:

interval 在哪里运行?你能告诉我们完整的组件代码吗?或者更好的是,创建一个重现问题的代码框? useEffect 没有触发,因为依赖是一个对象 gameData,useEffect 不会在嵌套属性更改时调用。更多信息:***.com/questions/56010536/… 这其实是个好问题。我做了一个更简单的演示,显示useState 变量在setInterval 内部没有更新。 但是使用useRef 解决了这个问题。 codesandbox.io/s/youthful-wing-fdegd?file=/src/App.js @MoshFeu 当 setTimer 运行时,您将一个函数传递给 setTimeout,该函数关闭变量,但 setTimer 仅在您单击按钮时运行,因此该函数仅在当时传递给 setTimeout 并关闭 @ 987654338@当时有。它正在关闭stale closure 的variable 这是一个很好的见解和参考@HMR,谢谢。 【参考方案1】:

如果您确定每次更新游戏数据时页面都会呈现,那么您可以这样做。

React.useEffect(() => 
    if(gameData?.game?.roundStarted) 
        if(isGameStarted == '0') 
            console.log("round is started");
            setIsGameStarted('1');
        
    
, [gameData?.game?.roundStarted]);

useEffect 不会遍历对象中的所有道具,因此您必须明确地将确切的值放入监视列表中。

【讨论】:

【参考方案2】:

在你传递给 setInterval 的函数中有一堆stale closures 这是一个工作定时器的例子:

const App = () => 
  const [
    stateInInterval,
    setStateInInterval,
  ] = React.useState(
    count: 0,
    running: false,
  );

  const interval = () => 
    setStateInInterval((current) => 
      if (!current.running) 
        clearInterval(current.interval);
        return current;
      
      if (current.count > 9) 
        return  ...current, running: false ;
      
      return 
        ...current,
        count: current.count + 1,
      ;
    );
  ;

  const startTimer = () => 
    if (stateInInterval.running) 
      //already running
      return;
    
    setStateInInterval((current) => (
      ...current,
      interval: setInterval(interval, 1000),
      running: true,
    ));
  ;
  const stopTimer = () => 
    setStateInInterval((current) => (
      ...current,
      running: false,
    ));
  ;
  const resetTimer = () => 
    setStateInInterval((current) => (
      ...current,
      count: 0,
    ));
  ;

  return (
    <div>
      <button onClick=startTimer>start timer</button>
      <button onClick=stopTimer>stop timer</button>
      <button onClick=resetTimer>reset timer</button>
      <h4>stateInInterval.count</h4>
    </div>
  );
;

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

【讨论】:

非常感谢那个链接,这篇文章终于让我明白了为什么会这样。

以上是关于反应useState不更新值的主要内容,如果未能解决你的问题,请参考以下文章

反应输入字段不使用 onClick 事件更新 useState

反应 useState 钩子不使用 axios 调用更新

反应 useState 总是给出以前的状态值

UseState 未使用 TextInput 正确更新 |反应

反应钩子:useState/context;无法读取未定义的属性“头像”/如何更新嵌套对象

在 useState 钩子中反应设置状态