Skip to content

Commit 2142033

Browse files
committed
refactor: optimize caching and data retrieval for conferences, events, and schedules
1 parent 804bdbc commit 2142033

10 files changed

Lines changed: 392 additions & 159 deletions

File tree

src/features/bookmarks/Bookmarks.tsx

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,38 @@
11
import { useEffect, useState, lazy, Suspense, startTransition } from "react";
22
import { Link, useSearchParams } from "react-router";
33

4-
import type { HTConference, HTEvent, HTTagGroup } from "@/types/db";
4+
import type { HTConference } from "@/types/db";
55
import type { GroupedSchedule } from "@/types/ht";
66

77
import { ConferenceHeader } from "@/components/ConferenceHeader";
88
import ErrorPage from "@/components/ErrorPage";
99
import { HTFooter } from "@/components/HTFooter";
1010
import LoadingPage from "@/components/LoadingPage";
11-
import { getConferenceByCode, getEvents, getTags } from "@/lib/db";
12-
import { buildScheduleBucketsByDay } from "@/lib/utils/schedule";
11+
import {
12+
filterScheduleByEventIds,
13+
getCachedConferenceSchedule,
14+
getConferenceSchedule,
15+
} from "@/lib/db";
1316
import { loadConfBookmarks } from "@/lib/utils/storage";
1417

1518
const EventsList = lazy(() => import("../schedule/EventsList"));
1619

1720
export function Bookmarks() {
1821
const [searchParams] = useSearchParams();
19-
const confCode = searchParams.get("conf");
22+
const confCode = searchParams.get("conf")?.trim().toUpperCase() ?? null;
23+
const [initial] = useState(() => {
24+
if (!confCode) return null;
25+
const schedule = getCachedConferenceSchedule(confCode);
26+
if (!schedule) return null;
27+
return {
28+
conference: schedule.conference,
29+
grouped: filterScheduleByEventIds(schedule.grouped, loadConfBookmarks(confCode)),
30+
};
31+
});
2032

21-
const [grouped, setGrouped] = useState<GroupedSchedule | null>(null);
22-
const [conference, setConference] = useState<HTConference | null>(null);
23-
const [loading, setLoading] = useState(false);
33+
const [grouped, setGrouped] = useState<GroupedSchedule | null>(initial?.grouped ?? null);
34+
const [conference, setConference] = useState<HTConference | null>(initial?.conference ?? null);
35+
const [loading, setLoading] = useState(!initial);
2436
const [error, setError] = useState<string | null>(null);
2537

2638
useEffect(() => {
@@ -41,40 +53,41 @@ export function Bookmarks() {
4153
async function run() {
4254
if (!confCode) {
4355
setError("Missing required URL parameters.");
56+
setLoading(false);
4457
return;
4558
}
46-
setLoading(true);
4759
setError(null);
48-
setGrouped(null);
49-
setConference(null);
60+
61+
const cachedSchedule = getCachedConferenceSchedule(confCode);
62+
const bookmarks = loadConfBookmarks(confCode);
63+
if (cachedSchedule) {
64+
setConference(cachedSchedule.conference);
65+
setGrouped(filterScheduleByEventIds(cachedSchedule.grouped, bookmarks));
66+
setLoading(false);
67+
} else {
68+
setLoading(true);
69+
setGrouped(null);
70+
setConference(null);
71+
}
5072

5173
try {
52-
const conf = await getConferenceByCode(confCode);
74+
const schedule = await getConferenceSchedule(confCode);
5375
if (cancelled) return;
54-
if (!conf) {
76+
if (!schedule) {
5577
setError(`Conference not found.`);
5678
return;
5779
}
58-
setConference(conf);
5980

60-
const bookmarks = loadConfBookmarks(confCode); // Set<number>
6181
if (bookmarks.size === 0) {
82+
setConference(schedule.conference);
6283
setGrouped({});
6384
return;
6485
}
6586

66-
const [evs, tags] = await Promise.all([getEvents(confCode), getTags(confCode)]);
67-
if (cancelled) return;
68-
69-
const tz = conf.timezone || "UTC";
70-
const bookmarkedEvents = (evs as HTEvent[]).filter((e) => bookmarks.has(e.id));
71-
const groupedSchedule = buildScheduleBucketsByDay(
72-
bookmarkedEvents,
73-
tags as HTTagGroup[],
74-
tz,
75-
);
87+
const groupedSchedule = filterScheduleByEventIds(schedule.grouped, bookmarks);
7688

7789
startTransition(() => {
90+
setConference(schedule.conference);
7891
setGrouped(groupedSchedule);
7992
});
8093
} catch (e) {
@@ -91,7 +104,7 @@ export function Bookmarks() {
91104
};
92105
}, [confCode]);
93106

94-
if (loading) return <LoadingPage message="Loading bookmarks..." />;
107+
if (loading && !grouped && !conference) return <LoadingPage message="Loading bookmarks..." />;
95108

96109
if (error) return <ErrorPage msg={error} />;
97110

src/features/event/Event.tsx

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,51 @@ import { ConferenceHeader } from "@/components/ConferenceHeader";
77
import ErrorPage from "@/components/ErrorPage";
88
import { HTFooter } from "@/components/HTFooter";
99
import LoadingPage from "@/components/LoadingPage";
10-
import { getConferenceByCode, getEventById, getSpeakersByIds, getTags } from "@/lib/db";
10+
import {
11+
getCachedConferenceByCode,
12+
getCachedConferenceSchedule,
13+
getCachedEventById,
14+
getCachedTags,
15+
getConferenceByCode,
16+
getEventById,
17+
getSpeakersByIds,
18+
getTags,
19+
} from "@/lib/db";
1120
import { useNormalizedParams } from "@/lib/utils/params";
12-
import { toProcessedEvent } from "@/lib/utils/schedule";
21+
import { buildAllTagIndex, toProcessedEvent } from "@/lib/utils/schedule";
1322

1423
import EventDetails from "./EventDetails";
1524

25+
function getCachedEventDetails(confCode: string, eventId: number) {
26+
const schedule = getCachedConferenceSchedule(confCode);
27+
const event = schedule?.grouped
28+
? Object.values(schedule.grouped)
29+
.flat()
30+
.find((candidate) => candidate.id === eventId)
31+
: null;
32+
if (schedule?.conference && event) return { conference: schedule.conference, event };
33+
34+
const conference = getCachedConferenceByCode(confCode);
35+
const rawEvent = getCachedEventById(confCode, eventId);
36+
const tags = getCachedTags(confCode);
37+
if (!conference || !rawEvent || !tags) return null;
38+
39+
return {
40+
conference,
41+
event: toProcessedEvent(rawEvent, buildAllTagIndex(tags)),
42+
};
43+
}
44+
1645
export function Event() {
1746
const { confCode, eventId } = useNormalizedParams();
47+
const [initial] = useState(() =>
48+
confCode && eventId ? getCachedEventDetails(confCode, eventId) : null,
49+
);
1850

19-
const [event, setEvent] = useState<ProcessedEvent | null>(null);
51+
const [event, setEvent] = useState<ProcessedEvent | null>(initial?.event ?? null);
2052
const [people, setPeople] = useState<HTPerson[]>([]);
21-
const [conference, setConference] = useState<HTConference | null>(null);
22-
const [loading, setLoading] = useState(false);
53+
const [conference, setConference] = useState<HTConference | null>(initial?.conference ?? null);
54+
const [loading, setLoading] = useState(!initial);
2355
const [error, setError] = useState<string | null>(null);
2456

2557
// Title reflects state
@@ -39,6 +71,7 @@ export function Event() {
3971
useEffect(() => {
4072
if (!confCode || !eventId) {
4173
setError("Missing required URL parameters.");
74+
setLoading(false);
4275
} else {
4376
setError(null);
4477
}
@@ -50,11 +83,21 @@ export function Event() {
5083
async function run() {
5184
if (!confCode || !eventId) return;
5285

53-
setLoading(true);
5486
setError(null);
55-
setEvent(null);
56-
setPeople([]);
57-
setConference(null);
87+
88+
const cachedDetails = getCachedEventDetails(confCode, eventId);
89+
90+
if (cachedDetails) {
91+
setConference(cachedDetails.conference);
92+
setEvent(cachedDetails.event);
93+
setPeople([]);
94+
setLoading(false);
95+
} else {
96+
setLoading(true);
97+
setEvent(null);
98+
setPeople([]);
99+
setConference(null);
100+
}
58101

59102
try {
60103
const conf = await getConferenceByCode(confCode);
@@ -108,7 +151,7 @@ export function Event() {
108151
};
109152
}, [confCode, eventId]);
110153

111-
if (loading) {
154+
if (loading && !conference && !event) {
112155
return (
113156
<div className="flex min-h-screen flex-col bg-gray-950">
114157
<main className="flex-1">

src/features/schedule/Schedule.tsx

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { useEffect, useState, lazy, Suspense, useTransition } from "react";
22

3-
import type { HTConference, HTEvent, HTTagGroup } from "@/types/db";
3+
import type { HTConference } from "@/types/db";
44
import type { GroupedSchedule } from "@/types/ht";
55

66
import { ConferenceHeader } from "@/components/ConferenceHeader";
77
import ErrorPage from "@/components/ErrorPage";
88
import LoadingPage from "@/components/LoadingPage";
9-
import { getConferenceByCode, getEvents, getTags } from "@/lib/db";
9+
import { getCachedConferenceSchedule, getConferenceSchedule } from "@/lib/db";
1010
import { useNormalizedParams } from "@/lib/utils/params";
11-
import { buildScheduleBucketsByDay } from "@/lib/utils/schedule";
1211

1312
const EventsList = lazy(() => import("./EventsList"));
1413
let eventsListPreload: Promise<unknown> | null = null;
@@ -21,10 +20,16 @@ function preloadEventsList() {
2120

2221
export function Schedule() {
2322
const { confCode } = useNormalizedParams();
23+
const [initialSchedule] = useState(() =>
24+
confCode ? getCachedConferenceSchedule(confCode) : null,
25+
);
2426

25-
const [grouped, setGrouped] = useState<GroupedSchedule | null>(null);
26-
const [conference, setConference] = useState<HTConference | null>(null);
27+
const [grouped, setGrouped] = useState<GroupedSchedule | null>(initialSchedule?.grouped ?? null);
28+
const [conference, setConference] = useState<HTConference | null>(
29+
initialSchedule?.conference ?? null,
30+
);
2731
const [error, setError] = useState<string | null>(null);
32+
const [loading, setLoading] = useState(!initialSchedule);
2833
const [isPending, startTransition] = useTransition();
2934

3035
useEffect(() => {
@@ -34,52 +39,60 @@ export function Schedule() {
3439
useEffect(() => {
3540
const id = window.setTimeout(() => {
3641
if (error) document.title = "Error · Schedule | Hacker Tracker";
37-
else if (isPending && !grouped) document.title = "Loading schedule… | Hacker Tracker";
42+
else if (loading && !grouped) document.title = "Loading schedule… | Hacker Tracker";
3843
else if (conference) document.title = `Schedule · ${conference.name} | Hacker Tracker`;
3944
else document.title = "Schedule | Hacker Tracker";
4045
}, 150); // small debounce
4146
return () => clearTimeout(id);
42-
}, [conference, grouped, error, isPending]);
47+
}, [conference, grouped, error, loading]);
4348

4449
useEffect(() => {
4550
let cancelled = false;
4651

4752
async function load() {
48-
if (!confCode) return;
53+
if (!confCode) {
54+
setError("Missing required URL parameters.");
55+
setLoading(false);
56+
return;
57+
}
4958
setError(null);
5059

60+
const cachedSchedule = getCachedConferenceSchedule(confCode);
61+
if (cachedSchedule) {
62+
setConference(cachedSchedule.conference);
63+
setGrouped(cachedSchedule.grouped);
64+
setLoading(false);
65+
} else {
66+
setConference(null);
67+
setGrouped(null);
68+
setLoading(true);
69+
}
70+
5171
try {
52-
const conf = await getConferenceByCode(confCode);
72+
const schedule = await getConferenceSchedule(confCode);
5373
if (cancelled) return;
5474

55-
if (!conf) {
75+
if (!schedule) {
5676
setError("Conference not found.");
5777
return;
5878
}
5979

60-
const preload = preloadEventsList();
61-
62-
const [evs, tags] = await Promise.all([getEvents(confCode), getTags(confCode), preload]);
80+
await preloadEventsList();
6381
if (cancelled) return;
6482

65-
const tz = conf.timezone || "UTC";
66-
const groupedSchedule = buildScheduleBucketsByDay(
67-
evs as HTEvent[],
68-
tags as HTTagGroup[],
69-
tz,
70-
);
71-
7283
startTransition(() => {
7384
if (!cancelled) {
74-
setConference(conf);
75-
setGrouped(groupedSchedule);
85+
setConference(schedule.conference);
86+
setGrouped(schedule.grouped);
7687
}
7788
});
7889
} catch (e) {
7990
if (!cancelled) {
8091
const msg = e instanceof Error ? e.message : "Failed to load schedule";
8192
setError(msg);
8293
}
94+
} finally {
95+
if (!cancelled) setLoading(false);
8396
}
8497
}
8598

@@ -89,7 +102,7 @@ export function Schedule() {
89102
};
90103
}, [confCode]);
91104

92-
if (!grouped && !conference && !error && isPending) {
105+
if (!grouped && !conference && !error && loading) {
93106
return <LoadingPage message="Loading schedule..." />;
94107
}
95108
if (error) return <ErrorPage msg={error} />;
@@ -99,7 +112,7 @@ export function Schedule() {
99112
{conference && <ConferenceHeader conference={conference} />}
100113

101114
<main className="relative flex-1">
102-
{isPending && (
115+
{(loading || isPending) && grouped && (
103116
<div className="bg-background/40 pointer-events-none absolute inset-0 backdrop-blur-[1px] transition-opacity" />
104117
)}
105118

0 commit comments

Comments
 (0)