1

i am working in react typescript, i have used useState to update the value, but that value is not updating in the functions, i have used const [isGameStarted, setIsGameStarted] = React.useState<any>('0');, and i am updating its value in useEffect() function,

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

here I have updated its value as 1, but for my interval function it is not updating that value, here I have mentioned my interval function, this interval function call every 1 second, but it always considers isGameStarted value as 0, can anyone please help me why it is not getting value as 1 even after that useEffect() function call, any help will be really appreciated

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

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

Full Code :

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>
    );
};
reymon359
  • 1,240
  • 2
  • 11
  • 34
Nikul Panchal
  • 1,542
  • 3
  • 35
  • 58
  • Where the `interval` is running? Can you show us the full component code? Or even better, create a codesnadbox which reproduce the issue? – Mosh Feu Jul 08 '20 at 08:27
  • 1
    useEffect is not triggering because the dependency is an object gameData, useEffect doesnt invoke on nested property change. more info: https://stackoverflow.com/questions/56010536/react-hooks-trigger-useeffect-when-a-nested-property-changes-in-a-collection-o – bakar_dev Jul 08 '20 at 08:29
  • Did you see the console log - "round is started"? – D10S Jul 08 '20 at 08:33
  • let me update my whole code – Nikul Panchal Jul 08 '20 at 08:33
  • yes round started is working – Nikul Panchal Jul 08 '20 at 08:33
  • @MoshFeu i have added my full code, can you please test it – Nikul Panchal Jul 08 '20 at 08:35
  • I am not sure what the problem is but you should change the "any" type to "string" and change "==" to "===" (it is not going to solve the problem anyhow). – D10S Jul 08 '20 at 08:35
  • Not sure that this is the case but it might be because it is inside "setInterval". Check out the following: https://stackoverflow.com/a/53024497/11407096 – D10S Jul 08 '20 at 08:45
  • 1
    This is actually a good question. I made a simpler demo which shows that a `useState` variable doesn't updated inside `setInterval`. **But** using `useRef` solves the problem. https://codesandbox.io/s/youthful-wing-fdegd?file=/src/App.js – Mosh Feu Jul 08 '20 at 08:54
  • 1
    @MoshFeu When setTimer runs you pass a function to setTimeout that closes over variable but setTimer only runs when you click the button so the function is only passed to setTimeout at that time and closes over the value that `variable` has at that time. It is closing over a [stale closure](https://dmitripavlutin.com/react-hooks-stale-closures/) of `variable`. – HMR Jul 08 '20 at 09:14
  • 1
    This is a good insight and reference @HMR, thanks. – Mosh Feu Jul 08 '20 at 09:28

2 Answers2

3

In the function you pass to setInterval there are a bunch of stale closures here is an example of a working timer:

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>
HMR
  • 37,593
  • 24
  • 91
  • 160
2

If you are sure that the page renders everytime gameData is updated, then you can do this.

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

useEffect doesn't traverse through all props in an object so you have to explicitly put the exact value in the watch list.

z.a.
  • 2,549
  • 3
  • 12
  • 16