0

right now i have 2 components, my parent component is Game and from that it calls the child component, GameInner, from GameInner component i have define the useEffect, but it calls 2 times, i don't know why. this is my useEffect function, also i have define both component code here,

React.useEffect(() => { if(!isCalled.current) { isCalled.current = true; if(started) { console.log('i fire once') if(isCalled.current) { getUpdate(); } } } }, []); 

Game.tsx

import {Redirect, RouteComponentProps, withRouter} from "react-router"; import React, { useEffect, useState } from "react"; import {GameDataStore, GameDataStorePayload} from "../../Global/DataStore/GameDataStore"; import {UserData, UserDataStore} from "../../Global/DataStore/UserDataStore"; import Helmet from "react-helmet"; import {Dialog, DialogContent, Typography} from "@material-ui/core"; import {ContainerProgress} from "../../UI/ContainerProgress"; import {LoadingButton} from "../../UI/LoadingButton"; import {Support} from "./Components/Gameplay/Support"; import {GameChatFab} from "./Components/Chat/GameChatFab"; import {ChatSidebar} from "./Components/Chat/ChatSidebar"; import {GameInner} from "./Components/Gameplay/GameInner"; import {SocketDataStore, SocketDataStorePayload} from "../../Global/DataStore/SocketDataStore"; import moment from "moment"; import {getTrueRoundsToWin} from "../../Global/Utils/GameUtils"; import {ClientGameItem} from "../../Global/Platform/Contract"; import {PlayerJoinApproval} from "@Areas/Game/Components/Gameplay/PlayerJoinApproval"; interface IGameParams { id: string; } interface IGameState { socketData: SocketDataStorePayload; gameData: GameDataStorePayload; userData: UserData; restartLoading: boolean; restartDelayed: boolean; showSupport: boolean; chatDrawerOpen: boolean; } class Game extends React.Component<RouteComponentProps<IGameParams>, IGameState> { private supportDelayTimeout = 0; constructor(props: RouteComponentProps<IGameParams>) { super(props); this.state = { socketData: SocketDataStore.state, gameData: GameDataStore.state, userData: UserDataStore.state, restartLoading: false, restartDelayed: true, showSupport: false, chatDrawerOpen: true }; } public componentDidMount(): void { GameDataStore.hydrate(this.props.match.params.id); SocketDataStore.listen(data => this.setState({ socketData: data })); GameDataStore.listen(data => this.setState({ gameData: data })); UserDataStore.listen(data => this.setState({ userData: data })); } private getWinnerFromState(state: IGameState) { const { players, settings } = state.gameData.game ?? {}; const playerGuids = Object.keys(players ?? {}); const roundsToWin = getTrueRoundsToWin(state.gameData.game as ClientGameItem); const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin); return winnerGuid; } public componentDidUpdate(prevProps: Readonly<RouteComponentProps<IGameParams>>, prevState: Readonly<IGameState>, snapshot?: any): void { const hadWinner = this.getWinnerFromState(prevState); const hasWinner = this.getWinnerFromState(this.state); if (!hadWinner && hasWinner && this.supportDelayTimeout === 0) { this.supportDelayTimeout = window.setTimeout(() => { this.setState({ restartDelayed: true, showSupport: true }); setTimeout(() => this.setState({ restartDelayed: false }), 5000); }, 2000); } } private restartClick = (playerGuid: string) => { this.setState({ restartLoading: true }); GameDataStore.restart(playerGuid) .finally(() => this.setState({ restartLoading: false })); }; public render() { const { id, } = this.props.match.params; if (!id) { return <Redirect to={"/"}/>; } const { dateCreated, ownerGuid, spectators, pendingPlayers, players, settings, } = this.state.gameData.game ?? {}; if (!this.state.gameData.game || !this.state.gameData.loaded || !this.state.socketData.hasConnection) { return <ContainerProgress/>; } const { playerGuid } = this.state.userData; const owner = players?.[ownerGuid ?? ""]; const amInGame = playerGuid in (players ?? {}); const amSpectating = playerGuid in {...(spectators ?? {}), ...(pendingPlayers ?? {})}; const title = `${unescape(owner?.nickname ?? "")}'s game`; const playerGuids = Object.keys(players ?? {}); const roundsToWin = getTrueRoundsToWin(this.state.gameData.game as ClientGameItem); const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin); const canChat = (amInGame || amSpectating) && moment(dateCreated).isAfter(moment(new Date(1589260798170))); return ( <> <Helmet> <title>{title}</title> </Helmet> <PlayerJoinApproval/> <GameInner gameId={id} /> {winnerGuid && ( <Dialog open={this.state.showSupport} onClose={() => this.setState({showSupport: false})}> <DialogContent style={{padding: "2rem"}}> <Typography variant={"h6"} style={{textAlign: "center"}}> Game over! {unescape(players?.[winnerGuid].nickname ?? "")} is the winner. </Typography> <Support/> {playerGuid === ownerGuid && ( <div style={{ marginTop: "7rem", textAlign: "center" }}> <LoadingButton loading={this.state.restartLoading || this.state.restartDelayed} variant={"contained"} color={"secondary"} onClick={() => this.restartClick(playerGuid)}> Restart this game? </LoadingButton> </div> )} </DialogContent> </Dialog> )} {canChat && ( <> <GameChatFab showChat={amInGame || amSpectating}/> <ChatSidebar /> </> )} </> ); } }; export default withRouter(Game); 

GameInner.tsx

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); let setSeconds = 30; const isGameStarted = React.useRef(false); const isCalled = React.useRef(false); /******************* interval timer ****************/ /***************************************************/ 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" })); } }, [started, iWasKicked, amInGame]); React.useEffect(() => { if(!isCalled.current) { isCalled.current = true; if(started) { console.log('i fire once') if(isCalled.current) { getUpdate(); } } } }, []); React.useEffect(() => { if(gameData?.game?.roundStarted) { if(!isGameStarted.current) { console.log("round is started"); isGameStarted.current = true; } } }, [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'); if(!isGameStarted.current) { console.log("isGameStarted : "+isGameStarted.current); 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) { 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} {(!started || !(amInGame || amSpectating)) && ( <BeforeGame gameId={gameId} isOwner={isOwner} /> )} {started && amInGame && !isChooser && ( [ <GamePlayWhite /> ] )} {started && amInGame && isChooser && ( [ <GamePlayBlack /> ] )} {started && amSpectating && ( <GamePlaySpectate /> )} </ErrorBoundary> )} </div> </div> ); }; interface BeforeGameProps { isOwner: boolean; gameId: string; } const BeforeGame: React.FC<BeforeGameProps> = (props) => { return ( <> {props.isOwner && ( <GameStart id={props.gameId} /> )} {!props.isOwner && ( <GameJoin id={props.gameId} /> )} </> ); }; 
3
  • Something is causing GameInner to remount at some point. Maybe it's a side effect of mixing functional component style with hooks? I can't say for certain. Commented Jul 8, 2020 at 12:19
  • can you help me what should i do ? Commented Jul 8, 2020 at 12:20
  • It's very hard to tell from just the code you've provided. Can you make a minimal reproducible example in something like Codepen/Codesandbox? Commented Jul 8, 2020 at 12:21

3 Answers 3

4

Does the double render also occur in Production (i.e. build) mode and are you using Strict Mode?

Strict Mode will render your components twice in dev, which means your useEffect will be called twice.

Sign up to request clarification or add additional context in comments.

4 Comments

i tried with strict: false, but still getting same issue
Not with tsconfig. If you used create-react-app it should have <StrictMode> wrapped around your app in index.js.
let me check it
strictmode is not there i just checked
0

It must be the gameId that initially has no value (First call to useEffect) and gets populated by Match component of router library (Second call to useEffect)

You can test this by giving a hardcoded value to gameId where it should only call useEffect once:

<GameInner gameId={"5"} /> // hardcoded id variable 

Regarding prevention; I think its pretty normal with react, as long as you have proper control inside useEffect implementation.

2 Comments

it call 2 times even after static id
i want to update this function getUpdate() only once, but because of useEffect call twice, it runs 2 times
0

If someone else finds this post: I think it's the useMediaQuery that initially returns false even though the resolution is narrow enough for tablet. And then just moments later the correct value is returned and another component is rendered.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.