feat : improve game loading flow
This commit is contained in:
24
src/hooks/useDebounce.ts
Normal file
24
src/hooks/useDebounce.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useDebounce<T>(value: T, delayMs: number): T {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value === debouncedValue) return;
|
||||||
|
|
||||||
|
if (!debouncedValue) {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
}, delayMs);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, delayMs, debouncedValue]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||||
import { getChessComUserRecentGames } from "@/lib/chessCom";
|
import { getChessComUserRecentGames } from "@/lib/chessCom";
|
||||||
import { capitalize } from "@/lib/helpers";
|
import { capitalize } from "@/lib/helpers";
|
||||||
import { ChessComGame } from "@/types/chessCom";
|
|
||||||
import {
|
import {
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -11,42 +10,31 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { boardOrientationAtom } from "../analysis/states";
|
import { boardOrientationAtom } from "../analysis/states";
|
||||||
|
import { useDebounce } from "@/hooks/useDebounce";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onSelect: (pgn: string) => void;
|
onSelect: (pgn: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChessComInput({ onSelect }: Props) {
|
export default function ChessComInput({ onSelect }: Props) {
|
||||||
const [requestCount, setRequestCount] = useState(0);
|
|
||||||
const [chessComUsername, setChessComUsername] = useLocalStorage(
|
const [chessComUsername, setChessComUsername] = useLocalStorage(
|
||||||
"chesscom-username",
|
"chesscom-username",
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
const debouncedUsername = useDebounce(chessComUsername, 200);
|
||||||
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
||||||
const [games, setGames] = useState<ChessComGame[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
if (!chessComUsername) {
|
data: games,
|
||||||
setGames([]);
|
isFetching,
|
||||||
return;
|
isError,
|
||||||
}
|
} = useQuery({
|
||||||
|
queryKey: ["CCUserGames", debouncedUsername],
|
||||||
const timeout = setTimeout(
|
enabled: !!debouncedUsername,
|
||||||
async () => {
|
queryFn: () => getChessComUserRecentGames(debouncedUsername ?? ""),
|
||||||
const games = await getChessComUserRecentGames(chessComUsername);
|
});
|
||||||
setGames(games);
|
|
||||||
},
|
|
||||||
requestCount === 0 ? 0 : 500
|
|
||||||
);
|
|
||||||
|
|
||||||
setRequestCount((prev) => prev + 1);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
}, [chessComUsername]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -68,7 +56,18 @@ export default function ChessComInput({ onSelect }: Props) {
|
|||||||
minHeight={100}
|
minHeight={100}
|
||||||
size={12}
|
size={12}
|
||||||
>
|
>
|
||||||
{games.map((game) => (
|
{isFetching ? (
|
||||||
|
<CircularProgress />
|
||||||
|
) : isError ? (
|
||||||
|
<span style={{ color: "salmon" }}>
|
||||||
|
User not found. Please check your username.
|
||||||
|
</span>
|
||||||
|
) : !games?.length ? (
|
||||||
|
<span style={{ color: "salmon" }}>
|
||||||
|
No games found. Please check your username.
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
games.map((game) => (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBoardOrientation(
|
setBoardOrientation(
|
||||||
@@ -97,9 +96,8 @@ export default function ChessComInput({ onSelect }: Props) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
{games.length === 0 && <CircularProgress />}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||||
import { getLichessUserRecentGames } from "@/lib/lichess";
|
import { getLichessUserRecentGames } from "@/lib/lichess";
|
||||||
import { capitalize } from "@/lib/helpers";
|
import { capitalize } from "@/lib/helpers";
|
||||||
import { LichessGame } from "@/types/lichess";
|
|
||||||
import {
|
import {
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -10,43 +9,32 @@ import {
|
|||||||
ListItemText,
|
ListItemText,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { boardOrientationAtom } from "../analysis/states";
|
import { boardOrientationAtom } from "../analysis/states";
|
||||||
|
import { useDebounce } from "@/hooks/useDebounce";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onSelect: (pgn: string) => void;
|
onSelect: (pgn: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LichessInput({ onSelect }: Props) {
|
export default function LichessInput({ onSelect }: Props) {
|
||||||
const [requestCount, setRequestCount] = useState(0);
|
|
||||||
const [lichessUsername, setLichessUsername] = useLocalStorage(
|
const [lichessUsername, setLichessUsername] = useLocalStorage(
|
||||||
"lichess-username",
|
"lichess-username",
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
const debouncedUsername = useDebounce(lichessUsername, 200);
|
||||||
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
||||||
const [games, setGames] = useState<LichessGame[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
if (!lichessUsername) {
|
data: games,
|
||||||
setGames([]);
|
isFetching,
|
||||||
return;
|
isError,
|
||||||
}
|
} = useQuery({
|
||||||
|
queryKey: ["LichessUserGames", debouncedUsername],
|
||||||
const timeout = setTimeout(
|
enabled: !!debouncedUsername,
|
||||||
async () => {
|
queryFn: () => getLichessUserRecentGames(debouncedUsername ?? ""),
|
||||||
const games = await getLichessUserRecentGames(lichessUsername);
|
});
|
||||||
setGames(games);
|
|
||||||
},
|
|
||||||
requestCount === 0 ? 0 : 500
|
|
||||||
);
|
|
||||||
|
|
||||||
setRequestCount((prev) => prev + 1);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
}, [lichessUsername]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -68,7 +56,18 @@ export default function LichessInput({ onSelect }: Props) {
|
|||||||
minHeight={100}
|
minHeight={100}
|
||||||
size={12}
|
size={12}
|
||||||
>
|
>
|
||||||
{games.map((game) => (
|
{isFetching ? (
|
||||||
|
<CircularProgress />
|
||||||
|
) : isError ? (
|
||||||
|
<span style={{ color: "salmon" }}>
|
||||||
|
User not found. Please check your username.
|
||||||
|
</span>
|
||||||
|
) : !games?.length ? (
|
||||||
|
<span style={{ color: "salmon" }}>
|
||||||
|
No games found. Please check your username.
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
games.map((game) => (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBoardOrientation(
|
setBoardOrientation(
|
||||||
@@ -99,9 +98,8 @@ export default function LichessInput({ onSelect }: Props) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
{games.length === 0 && <CircularProgress />}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -104,7 +104,10 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
|||||||
displayEmpty
|
displayEmpty
|
||||||
input={<OutlinedInput label="Game origin" />}
|
input={<OutlinedInput label="Game origin" />}
|
||||||
value={gameOrigin ?? ""}
|
value={gameOrigin ?? ""}
|
||||||
onChange={(e) => setGameOrigin(e.target.value as GameOrigin)}
|
onChange={(e) => {
|
||||||
|
setGameOrigin(e.target.value as GameOrigin);
|
||||||
|
setParsingError("");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{Object.values(GameOrigin).map((origin) => (
|
{Object.values(GameOrigin).map((origin) => (
|
||||||
<MenuItem key={origin} value={origin}>
|
<MenuItem key={origin} value={origin}>
|
||||||
@@ -128,7 +131,7 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
|||||||
|
|
||||||
{parsingError && (
|
{parsingError && (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<Typography color="red" textAlign="center" marginTop={1}>
|
<Typography color="salmon" textAlign="center" marginTop={1}>
|
||||||
{parsingError}
|
{parsingError}
|
||||||
</Typography>
|
</Typography>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user