Skip to content

Commit

Permalink
fixed context API and localStorage(fixed warning for data mismatch be…
Browse files Browse the repository at this point in the history
…tween server and client side rendering)
  • Loading branch information
VaishnavGhenge committed Jul 10, 2023
1 parent d4dc017 commit fba2c1f
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 107 deletions.
9 changes: 2 additions & 7 deletions components/ColorPicker/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,17 @@ import Color from "./Color";
import cross from "public/images/cross.png";
import Image from "next/dist/client/image";

type Player = {
name: string;
coinColor: string;
};

type Props = {
color: string;
setPlayer: React.Dispatch<React.SetStateAction<Player>>;
handleColorChange: (color: string) => void;
setToggleColorPicker: React.Dispatch<React.SetStateAction<boolean>>;
};

const ColorPicker = (props: Props) => {
const [inputHex, setInputHex] = React.useState<string>("");

const setColor = (color: string) => {
props.setPlayer((prev) => ({ ...prev, coinColor: color }));
props.handleColorChange(color);
props.setToggleColorPicker(false);
};

Expand Down
83 changes: 42 additions & 41 deletions components/Player/PlayerContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode, createContext, useContext, useEffect, useState } from "react";
import { ReactNode, createContext, useState } from "react";
import localStore from "utils/PlayerData";
import { getRandomUsername } from "utils/FakeUsername";
import { getRandomPlayer } from "utils/DummyPlayer";

interface Player {
name: string;
Expand All @@ -9,73 +9,74 @@ interface Player {

interface IPlayerContext {
getPlayer: (type: string) => Player;
setPlayer: (type: string, player: Player) => void;
setPlayer: (type: string, player: Player) => boolean;
tempAssignPlayer: (type: string) => Player;
}

export const PlayerContext = createContext<IPlayerContext>({
getPlayer: () => {
getPlayer: (type: string) => {
return {
name: "",
coinColor: "",
};
},
setPlayer: (type: string, player: Player) => false,
tempAssignPlayer(type) {
return {
name: "",
coinColor: "",
};
},
setPlayer: () => {},
});

const PlayerProvider = ({ children }: { children: ReactNode }) => {
const [playerA, setPlayerA] = useState<Player>(initializePlayer("A"));
const [playerA, setPlayerA] = useState<Player>(getRandomPlayer("A"));
const [playerB, setPlayerB] = useState<Player>(getRandomPlayer("B"));

const [playerB, setPlayerB] = useState<Player>(initializePlayer("B"));
// return player data from local storage if exists, otherwise return random player data
const getPlayer = (type: string) => {
const storedPlayer = localStore.getPlayerData(type);
if(storedPlayer) {
if(type === "A") setPlayerA(storedPlayer);
else setPlayerB(storedPlayer);

function initializePlayer(type: string) {
if(type === "A") {
const player = localStore.getPlayerData("A");
if(player) {
return player;
}
else {
const name = getRandomUsername()?.toString() || "random";
const coinColor = localStore.PLAYERA_COLOR;
localStore.setPlayerData("A", {name, coinColor});
return {name, coinColor};
}
return storedPlayer;
}
else {
const player = localStore.getPlayerData("B");
if(player) {
return player;
}
else {
const name = getRandomUsername()?.toString() || "random";
const coinColor = localStore.PLAYERB_COLOR;
localStore.setPlayerData("B", {name, coinColor});
return {name, coinColor};
}
}
}
if(type === "A") localStore.setPlayerData(type, playerA);
else localStore.setPlayerData(type, playerB);

const getPlayer = (type: string) => {
if(type === "A")
return playerA;
else if(type === "B")
return playerB;
else
return {} as Player;
return type === "A" ? playerA : playerB;
}
}

// set player data in local storage and context store
const setPlayer = (type: string, player: Player) => {
if(type === "A") {
setPlayerA(player);
localStore.setPlayerData("A", player);
}
else if(type === "B") {
setPlayerB(player);
localStore.setPlayerData("B", player);
}
else return false;

if(!localStore.setPlayerData(type, player)) return false;
return true;
}

// for temporary assignment of player data during server side rendering
const tempAssignPlayer = (type: string) => {
if(type === "A") {
return playerA;
}
else if(type === "B") {
return playerB;
}
else return { name: "", coinColor: "" }
}

return (
<PlayerContext.Provider value={{ getPlayer, setPlayer }}>
<PlayerContext.Provider value={{ getPlayer, setPlayer, tempAssignPlayer }}>
{children}
</PlayerContext.Provider>
);
Expand Down
58 changes: 27 additions & 31 deletions components/Player/PlayerPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useRef, useEffect, useState } from "react";
import { getPlayerAvatar } from "utils/PlayerAvatars";
import { getRandomUsername } from "utils/FakeUsername";
import ColorPicker from "components/ColorPicker/ColorPicker";
import { PlayerContext } from "./PlayerContext";

Expand All @@ -10,28 +9,18 @@ type Props = {

const PlayerPanel = (props: Props) => {
const contextStore = React.useContext(PlayerContext);
const [player, setPlayer] = useState(contextStore.getPlayer(props.playerAlphabet));
const [player, setPlayer] = useState(contextStore.tempAssignPlayer(props.playerAlphabet));
const [profile, setProfile] = useState<string>("");
const [toggleColorPicker, setToggleColorPicker] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null);
const isInitialClick = useRef<boolean>(true);
const [typingTimeout, setTypingTimeout] = useState<NodeJS.Timeout | null>(null);

// upon client load, check if player data is stored in local storage otherwise set it
useEffect(() => {
contextStore.setPlayer(props.playerAlphabet, {
name: player.name,
coinColor: player.coinColor,
});
}, [player.name, player.coinColor]);

useEffect(() => {
getPlayerAvatar(player.name)
.then((avatar) => {
setProfile(avatar);
})
.catch((err) => {
console.log(err);
});
const contextPlayer = contextStore.getPlayer(props.playerAlphabet);
setPlayer(contextPlayer);
updatePlayerAvatar(contextPlayer.name);

return () => {
if (typingTimeout) {
Expand All @@ -40,6 +29,18 @@ const PlayerPanel = (props: Props) => {
};
}, []);

// update avatar image (supposed to be called when player name is changed)
const updatePlayerAvatar = async (playerName: string) => {
// console.log(playerName, "updating avatar");
await getPlayerAvatar(playerName)
.then((avatar) => {
setProfile(avatar);
})
.catch((err) => {
console.log(err);
});
};

// when user will click on input having randomly generated text it will select whole text
const handleInputClick = () => {
if (isInitialClick.current) {
Expand All @@ -48,25 +49,27 @@ const PlayerPanel = (props: Props) => {
}
};

// when user will change input value, it will update player name and avatar
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;

setPlayer(prevPlayer => ({...prevPlayer, name: value}));
contextStore.setPlayer(props.playerAlphabet, {...player, name: value});

if (typingTimeout) clearTimeout(typingTimeout);

const newTypingTimeout = setTimeout(() => {
getPlayerAvatar(value)
.then((avatar) => {
setProfile(avatar);
})
.catch((err) => {
console.log(err);
});
updatePlayerAvatar(value);
}, 1000);

setTypingTimeout(newTypingTimeout);
};

const handleColorChange = (color: string) => {
setPlayer(prevPlayer => ({...prevPlayer, coinColor: color}));
contextStore.setPlayer(props.playerAlphabet, {...player, coinColor: color});
};

return (
<div
className={`flex flex-wrap justify-center flex-col rounded mt-5 bg-white p-3 w-2/3 sm:w-3/5 basis-1/2 ${
Expand Down Expand Up @@ -126,20 +129,13 @@ const PlayerPanel = (props: Props) => {
<div className="w-full">
<ColorPicker
color={player.coinColor}
setPlayer={setPlayer}
handleColorChange={handleColorChange}
setToggleColorPicker={setToggleColorPicker}
/>
</div>
)}
</div>
</div>

{/* <button
type="button"
className="bg-zinc-800 text-white py-3 text-center border-gray-400 rounded"
>
Ready
</button> */}
</div>
);
};
Expand Down
27 changes: 20 additions & 7 deletions components/game/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type MyProps = {
col: number;
dots: number;
getPlayer: (type: string) => Player; // Add getPlayer function to props
tempAssignPlayer: (type: string) => Player;
};

type MyState = {
Expand All @@ -29,6 +30,8 @@ type MyState = {
hoverTop: matrix_type[];
is_won: boolean;
message: string;
playerA: Player;
playerB: Player;
};

class BoardView extends Component<MyProps, MyState> {
Expand All @@ -47,9 +50,21 @@ class BoardView extends Component<MyProps, MyState> {
hoverTop: [0, 0, 0, 0, 0, 0, 0],
is_won: false,
message: "",
// assign both players from context store (temporarily/when rendering on server side) to avoid data mismatch warning
playerA: this.props.tempAssignPlayer("A"),
playerB: this.props.tempAssignPlayer("B"),
};
}

// when site will be rendered on client side, it will get player data from local storage/context store as localStorage only works on client side
componentDidMount(): void {
this.setState(prev => ({
...prev,
playerA: this.props.getPlayer("A"),
playerB: this.props.getPlayer("B"),
}));
}

// ui related
bgOnTop(col_num: number): void {
let newTop: matrix_type[] = [0, 0, 0, 0, 0, 0, 0];
Expand Down Expand Up @@ -228,8 +243,6 @@ class BoardView extends Component<MyProps, MyState> {
}

render() {
const { getPlayer } = this.props;

return (
<div className="flex flex-col justify-center">
{/* message */}
Expand Down Expand Up @@ -267,7 +280,7 @@ class BoardView extends Component<MyProps, MyState> {
</div>

<div className="flex justify-between items-center w-full">
<PlayerPanel player={getPlayer("A")} />
<PlayerPanel player={this.state.playerA} />
<div className="flex justify-center">
<div
style={{
Expand Down Expand Up @@ -316,8 +329,8 @@ class BoardView extends Component<MyProps, MyState> {
key={`in-${x_index}-${y_index}`}
turn={cell_value}
type="in-side"
coinColorA={getPlayer("A").coinColor}
coinColorB={getPlayer("B").coinColor}
coinColorA={this.state.playerA.coinColor}
coinColorB={this.state.playerB.coinColor}
onMouseOver={() => this.bgOnTop(y_index)}
onClick={() => this.is_winning(y_index)}
/>
Expand All @@ -327,7 +340,7 @@ class BoardView extends Component<MyProps, MyState> {
</div>
</div>
</div>
<PlayerPanel player={getPlayer("B")} />
<PlayerPanel player={this.state.playerB} />
</div>
</div>
);
Expand All @@ -337,7 +350,7 @@ class BoardView extends Component<MyProps, MyState> {
// Wraping the BoardView component with the HOC
const BoardViewWithPlayerContext = () => (
<PlayerContext.Consumer>
{({ getPlayer }) => <BoardView first_turn={1} row={6} col={7} dots={4} getPlayer={getPlayer} />}
{({ getPlayer, tempAssignPlayer }) => <BoardView first_turn={1} row={6} col={7} dots={4} getPlayer={getPlayer} tempAssignPlayer={tempAssignPlayer} />}
</PlayerContext.Consumer>
);

Expand Down
2 changes: 1 addition & 1 deletion pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const App = ({ Component, pageProps }: AppProps) => {
</div>

<PlayerProvider>
<main id="main-content" className="container pt-10">
<main id="main-content" className="container pt-2">
<Component {...pageProps} />
</main>
</PlayerProvider>
Expand Down
6 changes: 1 addition & 5 deletions pages/game.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import Head from "next/head";

import Instructions from "components/game/Instructions";
import Board from "components/game/Board";
import { PlayerContext } from "components/Player/PlayerContext";
import BoardViewWithPlayerContext from "components/game/Board";

export default function Game() {
return (
<>
{/* <div className="text-4xl font-bold text-center my-7">Connect 4</div> */}
<Head>
<title>C4 - Offline - 1 vs 1</title>
<title>Offline 1vs1</title>
</Head>

{/* <Instructions /> */}
<BoardViewWithPlayerContext />
<div className="mb-20"></div>
</>
);
}
Loading

0 comments on commit fba2c1f

Please sign in to comment.