feat : improve game loading flow

This commit is contained in:
GuillaumeSD
2025-05-09 01:53:34 +02:00
parent 5dc1c4f485
commit 34fdde282c
4 changed files with 137 additions and 114 deletions

24
src/hooks/useDebounce.ts Normal file
View 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;
}

View File

@@ -1,7 +1,6 @@
import { useLocalStorage } from "@/hooks/useLocalStorage";
import { getChessComUserRecentGames } from "@/lib/chessCom";
import { capitalize } from "@/lib/helpers";
import { ChessComGame } from "@/types/chessCom";
import {
CircularProgress,
FormControl,
@@ -11,42 +10,31 @@ import {
TextField,
} from "@mui/material";
import { useSetAtom } from "jotai";
import { useEffect, useState } from "react";
import { boardOrientationAtom } from "../analysis/states";
import { useDebounce } from "@/hooks/useDebounce";
import { useQuery } from "@tanstack/react-query";
interface Props {
onSelect: (pgn: string) => void;
}
export default function ChessComInput({ onSelect }: Props) {
const [requestCount, setRequestCount] = useState(0);
const [chessComUsername, setChessComUsername] = useLocalStorage(
"chesscom-username",
""
);
const debouncedUsername = useDebounce(chessComUsername, 200);
const setBoardOrientation = useSetAtom(boardOrientationAtom);
const [games, setGames] = useState<ChessComGame[]>([]);
useEffect(() => {
if (!chessComUsername) {
setGames([]);
return;
}
const timeout = setTimeout(
async () => {
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
const {
data: games,
isFetching,
isError,
} = useQuery({
queryKey: ["CCUserGames", debouncedUsername],
enabled: !!debouncedUsername,
queryFn: () => getChessComUserRecentGames(debouncedUsername ?? ""),
});
return (
<>
@@ -68,7 +56,18 @@ export default function ChessComInput({ onSelect }: Props) {
minHeight={100}
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
onClick={() => {
setBoardOrientation(
@@ -97,9 +96,8 @@ export default function ChessComInput({ onSelect }: Props) {
}}
/>
</ListItemButton>
))}
{games.length === 0 && <CircularProgress />}
))
)}
</Grid>
)}
</>

View File

@@ -1,7 +1,6 @@
import { useLocalStorage } from "@/hooks/useLocalStorage";
import { getLichessUserRecentGames } from "@/lib/lichess";
import { capitalize } from "@/lib/helpers";
import { LichessGame } from "@/types/lichess";
import {
CircularProgress,
FormControl,
@@ -10,43 +9,32 @@ import {
ListItemText,
TextField,
} from "@mui/material";
import { useEffect, useState } from "react";
import { useSetAtom } from "jotai";
import { boardOrientationAtom } from "../analysis/states";
import { useDebounce } from "@/hooks/useDebounce";
import { useQuery } from "@tanstack/react-query";
interface Props {
onSelect: (pgn: string) => void;
}
export default function LichessInput({ onSelect }: Props) {
const [requestCount, setRequestCount] = useState(0);
const [lichessUsername, setLichessUsername] = useLocalStorage(
"lichess-username",
""
);
const debouncedUsername = useDebounce(lichessUsername, 200);
const setBoardOrientation = useSetAtom(boardOrientationAtom);
const [games, setGames] = useState<LichessGame[]>([]);
useEffect(() => {
if (!lichessUsername) {
setGames([]);
return;
}
const timeout = setTimeout(
async () => {
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
const {
data: games,
isFetching,
isError,
} = useQuery({
queryKey: ["LichessUserGames", debouncedUsername],
enabled: !!debouncedUsername,
queryFn: () => getLichessUserRecentGames(debouncedUsername ?? ""),
});
return (
<>
@@ -68,7 +56,18 @@ export default function LichessInput({ onSelect }: Props) {
minHeight={100}
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
onClick={() => {
setBoardOrientation(
@@ -99,9 +98,8 @@ export default function LichessInput({ onSelect }: Props) {
}}
/>
</ListItemButton>
))}
{games.length === 0 && <CircularProgress />}
))
)}
</Grid>
)}
</>

View File

@@ -104,7 +104,10 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
displayEmpty
input={<OutlinedInput label="Game origin" />}
value={gameOrigin ?? ""}
onChange={(e) => setGameOrigin(e.target.value as GameOrigin)}
onChange={(e) => {
setGameOrigin(e.target.value as GameOrigin);
setParsingError("");
}}
>
{Object.values(GameOrigin).map((origin) => (
<MenuItem key={origin} value={origin}>
@@ -128,7 +131,7 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
{parsingError && (
<FormControl fullWidth>
<Typography color="red" textAlign="center" marginTop={1}>
<Typography color="salmon" textAlign="center" marginTop={1}>
{parsingError}
</Typography>
</FormControl>