Prevent unneccesary rerenders of the board on change
This commit is contained in:
parent
6a43b5c0dc
commit
5cc9574332
|
@ -22,6 +22,7 @@
|
|||
"myzod": "^1.0.0-alpha.9",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-fast-compare": "^3.2.0",
|
||||
"react-hook-form": "^5.6.3",
|
||||
"react-scripts": "^3.4.1",
|
||||
"react-use-websocket": "^2.0.1",
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import querystring from 'querystring';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ServerTimeProvider } from './hooks/useServerTime';
|
||||
import { ServerTimeProvider } from './hooks';
|
||||
import { Game, GameProps } from './pages/game';
|
||||
import { Login } from './pages/login';
|
||||
import { StaticView } from './pages/staticView';
|
||||
|
||||
export const App = (_props: {}) => {
|
||||
const [gameProps, setGameProps] = React.useState<GameProps | undefined>();
|
||||
const leave = React.useCallback(() => setGameProps(undefined), []);
|
||||
const onLogin = React.useCallback((roomID, nickname) => setGameProps({ roomID, nickname, leave }), [leave]);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const query = querystring.parse(window.location.search.substring(1));
|
||||
|
@ -24,9 +26,5 @@ export const App = (_props: {}) => {
|
|||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Login
|
||||
onLogin={(roomID, nickname) => setGameProps({ roomID, nickname, leave: () => setGameProps(undefined) })}
|
||||
/>
|
||||
);
|
||||
return <Login onLogin={onLogin} />;
|
||||
};
|
||||
|
|
|
@ -2,8 +2,9 @@ import { Button, createStyles, makeStyles, Theme, Typography } from '@material-u
|
|||
import { grey, orange, red } from '@material-ui/core/colors';
|
||||
import { Fireworks } from 'fireworks/lib/react';
|
||||
import * as React from 'react';
|
||||
import isEqual from 'react-fast-compare';
|
||||
|
||||
import { isDefined } from '../common';
|
||||
import { isDefined, noop } from '../common';
|
||||
import { StateBoard, StateTile } from '../protocol';
|
||||
import { TeamHue, teamSpecs } from '../teams';
|
||||
import { AspectDiv } from './aspectDiv';
|
||||
|
@ -85,20 +86,37 @@ const useTileStyles = makeStyles((theme: Theme) =>
|
|||
})
|
||||
);
|
||||
|
||||
const fireworksProps = {
|
||||
interval: 0,
|
||||
colors: [red[700], orange[800], grey[500]],
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
|
||||
interface TileProps {
|
||||
row: number;
|
||||
col: number;
|
||||
onClick: (row: number, col: number) => void;
|
||||
tile: StateTile;
|
||||
onClick: () => void;
|
||||
spymaster: boolean;
|
||||
myTurn: boolean;
|
||||
winner: boolean;
|
||||
}
|
||||
|
||||
const Tile = ({ tile, onClick, spymaster, myTurn, winner }: TileProps) => {
|
||||
const Tile = React.memo(function Tile({ row, col, onClick, tile, spymaster, myTurn, winner }: TileProps) {
|
||||
const classes = useTileStyles();
|
||||
|
||||
const bombRevealed = !!(tile.revealed && tile.view?.bomb);
|
||||
const alreadyExploded = React.useRef(bombRevealed);
|
||||
const explode = bombRevealed && !alreadyExploded.current;
|
||||
const disabled = spymaster || !myTurn || winner || tile.revealed;
|
||||
|
||||
const reveal = React.useMemo(() => {
|
||||
if (disabled) {
|
||||
return noop;
|
||||
}
|
||||
return () => onClick(row, col);
|
||||
}, [disabled, row, col, onClick]);
|
||||
|
||||
return (
|
||||
<AspectDiv aspectRatio="75%">
|
||||
|
@ -106,9 +124,9 @@ const Tile = ({ tile, onClick, spymaster, myTurn, winner }: TileProps) => {
|
|||
type="button"
|
||||
variant="contained"
|
||||
className={classes.button}
|
||||
onClick={onClick}
|
||||
onClick={reveal}
|
||||
style={tileStyle(tile, spymaster)}
|
||||
disabled={spymaster || !myTurn || winner || tile.revealed}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Typography variant="h6" className={classes.typo}>
|
||||
{tile.word}
|
||||
|
@ -117,20 +135,13 @@ const Tile = ({ tile, onClick, spymaster, myTurn, winner }: TileProps) => {
|
|||
{explode ? (
|
||||
<div className={classes.explosionWrapper}>
|
||||
<div className={classes.explosion}>
|
||||
<Fireworks
|
||||
{...{
|
||||
interval: 0,
|
||||
colors: [red[700], orange[800], grey[500]],
|
||||
x: 0,
|
||||
y: 0,
|
||||
}}
|
||||
/>
|
||||
<Fireworks {...fireworksProps} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</AspectDiv>
|
||||
);
|
||||
};
|
||||
}, isEqual);
|
||||
|
||||
export interface BoardProps {
|
||||
words: StateBoard;
|
||||
|
@ -154,7 +165,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||
})
|
||||
);
|
||||
|
||||
export const Board = (props: BoardProps) => {
|
||||
export const Board = React.memo(function Board(props: BoardProps) {
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
|
@ -163,8 +174,10 @@ export const Board = (props: BoardProps) => {
|
|||
arr.map((tile, col) => (
|
||||
<div key={row * props.words.length + col}>
|
||||
<Tile
|
||||
row={row}
|
||||
col={col}
|
||||
onClick={props.onClick}
|
||||
tile={tile}
|
||||
onClick={() => props.onClick(row, col)}
|
||||
spymaster={props.spymaster}
|
||||
myTurn={props.myTurn}
|
||||
winner={props.winner}
|
||||
|
@ -174,4 +187,4 @@ export const Board = (props: BoardProps) => {
|
|||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}, isEqual);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export interface ServerTime {
|
||||
setOffset: (v: number) => void;
|
||||
|
@ -16,3 +17,9 @@ export const ServerTimeProvider = (props: React.PropsWithChildren<{}>) => {
|
|||
export function useServerTime() {
|
||||
return React.useContext(Context);
|
||||
}
|
||||
|
||||
export function useStableUUID(): string {
|
||||
const id = React.useRef<string | undefined>();
|
||||
id.current = id.current ?? v4();
|
||||
return id.current;
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { fail } from 'assert';
|
||||
import * as React from 'react';
|
||||
import useWebSocket from 'react-use-websocket';
|
||||
import { v4 } from 'uuid';
|
||||
import { DeepReadonly } from 'ts-essentials';
|
||||
|
||||
import { assertIsDefined, assertNever, noop, reloadOutdatedPage, websocketUrl } from '../common';
|
||||
import { useServerTime } from '../hooks/useServerTime';
|
||||
import { useServerTime, useStableUUID } from '../hooks';
|
||||
import { version as codiesVersion } from '../metadata.json';
|
||||
import { ClientNote, PartialClientNote, ServerNote, State, StatePlayer, TimeResponse, WordPack } from '../protocol';
|
||||
import { GameView, Sender } from './gameView';
|
||||
|
@ -175,8 +175,8 @@ export interface GameProps {
|
|||
leave: () => void;
|
||||
}
|
||||
|
||||
export const Game = (props: GameProps) => {
|
||||
const [playerID] = React.useState(v4);
|
||||
export const Game = (props: DeepReadonly<GameProps>) => {
|
||||
const playerID = useStableUUID();
|
||||
const nickname = React.useRef(props.nickname); // Preserve a nickname for use in reconnects.
|
||||
|
||||
const syncTime = useSyncedServerTime();
|
||||
|
@ -184,6 +184,7 @@ export const Game = (props: GameProps) => {
|
|||
|
||||
const reducer = useStateReducer(sendJsonMessage);
|
||||
const [state, dispatch] = React.useReducer(reducer, undefined);
|
||||
const player = usePlayer(playerID, state);
|
||||
const send = useSender(dispatch);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -202,8 +203,6 @@ export const Game = (props: GameProps) => {
|
|||
}
|
||||
}, [lastJsonMessage]);
|
||||
|
||||
const player = usePlayer(playerID, state);
|
||||
|
||||
if (!state) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ import { Controller, useForm } from 'react-hook-form';
|
|||
import { isDefined, nameofFactory, noComplete } from '../common';
|
||||
import { Board } from '../components/board';
|
||||
import { ClipboardButton } from '../components/clipboard';
|
||||
import { useServerTime } from '../hooks/useServerTime';
|
||||
import { useServerTime } from '../hooks';
|
||||
import { State, StatePlayer, StateTimer, WordPack } from '../protocol';
|
||||
import { teamSpecs } from '../teams';
|
||||
|
||||
|
@ -511,10 +511,11 @@ const Sidebar = ({ send, state, pState, pTeam }: GameViewProps) => {
|
|||
|
||||
const Board2 = ({ send, state, pState, pTeam }: GameViewProps) => {
|
||||
const myTurn = state.turn === pTeam;
|
||||
|
||||
return (
|
||||
<Board
|
||||
words={state.board}
|
||||
onClick={(row, col) => myTurn && !pState.spymaster && send.reveal(row, col)}
|
||||
onClick={send.reveal}
|
||||
spymaster={pState.spymaster}
|
||||
myTurn={myTurn}
|
||||
winner={isDefined(state.winner)}
|
||||
|
@ -637,6 +638,15 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||
sidebar: {
|
||||
gridArea: 'sidebar',
|
||||
},
|
||||
leaveWrapper: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
margin: '0.5rem',
|
||||
},
|
||||
leaveButton: {
|
||||
marginRight: '0.5rem',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -645,15 +655,8 @@ export const GameView = (props: GameViewProps) => {
|
|||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
margin: '0.5rem',
|
||||
}}
|
||||
>
|
||||
<Button type="button" onClick={props.leave} startIcon={<ArrowBack />} style={{ marginRight: '0.5rem' }}>
|
||||
<div className={classes.leaveWrapper}>
|
||||
<Button type="button" onClick={props.leave} startIcon={<ArrowBack />} className={classes.leaveButton}>
|
||||
Leave
|
||||
</Button>
|
||||
<ClipboardButton
|
||||
|
|
|
@ -9037,6 +9037,11 @@ react-error-overlay@^6.0.7:
|
|||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
||||
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
|
||||
|
||||
react-fast-compare@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
||||
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
|
||||
|
||||
react-hook-form@^5.6.3:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-5.7.2.tgz#a84e259e5d37dd30949af4f79c4dac31101b79ac"
|
||||
|
|
Loading…
Reference in New Issue