From f37be8aded6b0c689fef38223ec134817ba5604a Mon Sep 17 00:00:00 2001 From: kyuuseiryuu Date: Fri, 30 Jan 2026 09:34:10 +0900 Subject: [PATCH] feat(global): HydrateFallback --- src/components/GameSelector/GameSelector.tsx | 48 ++++++++------- src/components/GameSelector/clubList.ts | 2 +- src/components/GroupingPrediction.tsx | 2 +- src/components/Tags.tsx | 1 - src/frontend.tsx | 63 ++++++++++++++------ src/page/ProfilePage.tsx | 1 + 6 files changed, 72 insertions(+), 45 deletions(-) diff --git a/src/components/GameSelector/GameSelector.tsx b/src/components/GameSelector/GameSelector.tsx index 4f5f56d..c829768 100644 --- a/src/components/GameSelector/GameSelector.tsx +++ b/src/components/GameSelector/GameSelector.tsx @@ -1,8 +1,8 @@ -import { Card, Divider, Flex, Select, Space, Statistic, Switch, Typography } from 'antd'; +import { Card, Divider, Flex, Select, Skeleton, Space, Statistic, Switch, Typography } from 'antd'; import type React from 'react'; import { useRequest } from 'ahooks'; import { clubs } from './clubList'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import dayjs from 'dayjs'; import type { IEventInfo } from '../../types'; @@ -11,25 +11,25 @@ interface Props { } export const GameSelector: React.FC = props => { - const requestEvents = useRequest( - async (clubId: string) => (await fetch(`/api/events/${clubId}`)).json() - , { manual: true }) - const [gameList, setGameList] = useState<(IEventInfo & { finished: boolean })[]>([]); - const [isEmpty, setIsEmpty] = useState(false); - const [clubId, setClubId] = useState(clubs[0].clubId); + const [clubId, setClubId] = useState(clubs[0]?.clubId ?? ''); + const requestEvents = useRequest( + async () => { + if (!clubId) return []; + return (await fetch(`/api/events/${clubId}`)).json() + }, { manual: false, refreshDeps: [clubId] }) const [showFinished, setShowFinished] = useState(false); - const handleClubChange = useCallback(async (clubId: string) => { - const list = await requestEvents.runAsync(clubId); - const activeList = list.map(e => ({ + const gameList = useMemo(() => { + const activeList = requestEvents.data?.map(e => ({ ...e, finished: e.info.join('').includes('已结束'), })); - setGameList(activeList); - setIsEmpty(activeList.filter(e => !e.finished).length === 0); - }, []); - useEffect(() => { - const clubId = clubs[0].clubId; - handleClubChange(clubId); + return activeList; + }, [requestEvents.data]); + const isEmpty = useMemo(() => { + return (gameList ?? []).filter(e => !e.finished).length === 0 + }, [gameList]); + const handleClubChange = useCallback(async (id: string) => { + setClubId(id); }, []); return ( @@ -48,12 +48,14 @@ export const GameSelector: React.FC = props => { /> {isEmpty && (没有未开始的比赛)} - - {gameList - .filter(e => showFinished || !e.finished) - .map(e => ) - } - + {requestEvents.loading ? : ( + + {gameList + ?.filter(e => showFinished || !e.finished) + ?.map(e => ) + } + + )} ); } diff --git a/src/components/GameSelector/clubList.ts b/src/components/GameSelector/clubList.ts index 5814ec9..32974ea 100644 --- a/src/components/GameSelector/clubList.ts +++ b/src/components/GameSelector/clubList.ts @@ -3,4 +3,4 @@ export const clubs = [ name: '东华', clubId: '47', }, -] as const; \ No newline at end of file +]; \ No newline at end of file diff --git a/src/components/GroupingPrediction.tsx b/src/components/GroupingPrediction.tsx index 566fe1b..da4d876 100644 --- a/src/components/GroupingPrediction.tsx +++ b/src/components/GroupingPrediction.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useState } from "react"; -import { Flex, Form, InputNumber, Space, Switch, Typography } from "antd"; +import { Flex, Form, InputNumber, Switch } from "antd"; import { chunk } from 'lodash'; import type { BasePlayer } from "../types"; import { GroupMember } from "./GroupMember"; diff --git a/src/components/Tags.tsx b/src/components/Tags.tsx index 5b027d2..b4b8a44 100644 --- a/src/components/Tags.tsx +++ b/src/components/Tags.tsx @@ -1,7 +1,6 @@ import { useRequest } from "ahooks"; import { Divider, Flex, Skeleton, Tag, Typography } from "antd"; import { EType, type XCXTag } from "../types"; -import { useEffect } from "react"; interface Props { uid?: string; diff --git a/src/frontend.tsx b/src/frontend.tsx index 166c3d3..63807d6 100644 --- a/src/frontend.tsx +++ b/src/frontend.tsx @@ -5,40 +5,65 @@ * It is included in `src/index.html`. */ -import { Component, StrictMode } from "react"; +import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { App } from "./App"; -import { ConfigProvider, theme } from "antd"; +import { ConfigProvider, Spin, theme } from "antd"; import { createBrowserRouter, RouterProvider } from "react-router"; import ProfilePage from "./page/ProfilePage"; import EventPage from "./page/EventPage"; import type { MatchInfo } from "./types"; +import { Outlet, useNavigation } from "react-router"; const elem = document.getElementById("root")!; const route = createBrowserRouter([ { path: '/', - element: - }, - { - path: '/event/:matchId', - loader: async ({ params }) => { - const info: MatchInfo = await (await fetch(`/api/match/${params.matchId}`)).json(); - const members = await (await fetch(`/api/match/${params.matchId}/${info.itemId}`)).json(); - return { info, members }; - }, - Component: EventPage, - }, - { - path: '/profile/:uid', - loader: async ({ params }) => { - return fetch(`/api/user/${params.uid}`); - }, - Component: ProfilePage, + element: , + children: [ + { + path: '', + index: true, + element: , + HydrateFallback: () => + }, + { + path: 'event/:matchId', + loader: async ({ params }) => { + const info: MatchInfo = await (await fetch(`/api/match/${params.matchId}`)).json(); + const members = await (await fetch(`/api/match/${params.matchId}/${info.itemId}`)).json(); + return { info, members }; + }, + Component: EventPage, + HydrateFallback: () => + }, + { + path: 'profile/:uid', + loader: async ({ params }) => { + return fetch(`/api/user/${params.uid}`); + }, + Component: ProfilePage, + HydrateFallback: () => + }, + ], }, ]); +function HydrateFallback() { + return ( + +
+ + ); +} + +function Layout() { + const navigation = useNavigation(); + const loading = navigation.state === 'loading'; + return loading ? : +} + const app = ( (); + console.debug('profile', profile); const navigate = useNavigate(); return ( <>