Skip to content

Commit 9fd86af

Browse files
committed
[Experiment] Chart fetching strategies
1 parent fb4d0a2 commit 9fd86af

File tree

16 files changed

+593
-162
lines changed

16 files changed

+593
-162
lines changed

apps/dashboard/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@sentry/nextjs": "8.45.1",
5050
"@shazow/whatsabi": "^0.18.0",
5151
"@tanstack/react-query": "5.62.16",
52+
"@tanstack/react-query-persist-client": "^5.64.2",
5253
"@tanstack/react-table": "^8.20.6",
5354
"@thirdweb-dev/service-utils": "workspace:*",
5455
"@vercel/functions": "^1.5.2",
@@ -64,6 +65,7 @@
6465
"flat": "^6.0.1",
6566
"framer-motion": "11.15.0",
6667
"fuse.js": "7.0.0",
68+
"idb-keyval": "^6.2.1",
6769
"input-otp": "^1.4.1",
6870
"ioredis": "^5.4.1",
6971
"ipaddr.js": "^2.2.0",

apps/dashboard/src/app/team/[team_slug]/(team)/layout.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ export default async function TeamLayout(props: {
8787
path: `/team/${params.team_slug}/~/settings`,
8888
name: "Settings",
8989
},
90+
{
91+
path: `/team/${params.team_slug}/~/test`,
92+
name: "Test",
93+
},
9094
]}
9195
/>
9296
</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"use client";
2+
3+
import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart";
4+
5+
export function ChartUI(props: {
6+
data: Array<{ time: Date; count: number }>;
7+
title: string;
8+
isPending: boolean;
9+
}) {
10+
return (
11+
<ThirdwebBarChart
12+
title={props.title}
13+
data={props.data}
14+
config={{
15+
count: {
16+
label: "Foo",
17+
color: "hsl(var(--chart-1))",
18+
},
19+
}}
20+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
21+
isPending={props.isPending}
22+
/>
23+
);
24+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function ignoreTime(date: Date) {
2+
const newDate = new Date(date);
3+
newDate.setHours(0, 0, 0, 0);
4+
return newDate;
5+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// This adds a key difference from the client side version -
2+
// client side version can skip this entirely on subsequent date range changes
3+
export async function simulatePageProcessingDelay() {
4+
await new Promise((resolve) => setTimeout(resolve, 1000));
5+
}
6+
7+
export async function simulateChartFetchingDelay() {
8+
await new Promise((resolve) => setTimeout(resolve, 2000));
9+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { addDays, differenceInCalendarDays } from "date-fns";
2+
import { simulateChartFetchingDelay } from "./delays";
3+
4+
export async function fetchTestData(params: {
5+
from: Date;
6+
to: Date;
7+
}) {
8+
await simulateChartFetchingDelay();
9+
10+
const days = differenceInCalendarDays(params.to, params.from);
11+
12+
const data: Array<{ time: Date; count: number }> = [];
13+
for (let i = 0; i < days; i++) {
14+
data.push({
15+
time: addDays(params.from, i),
16+
count: Math.floor(Math.random() * 1000),
17+
});
18+
}
19+
20+
return data;
21+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { getLastNDaysRange } from "../../../../../../../components/analytics/date-range-selector";
2+
import type { Range } from "../../../../../../../components/analytics/date-range-selector";
3+
import { ignoreTime } from "./date";
4+
import { simulatePageProcessingDelay } from "./delays";
5+
6+
export async function pageProcessing(props: {
7+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
8+
}) {
9+
const searchParams = await props.searchParams;
10+
const fromStr = searchParams.from;
11+
const toStr = searchParams.to;
12+
await simulatePageProcessingDelay();
13+
14+
const defaultRange = getLastNDaysRange("last-120");
15+
const range: Range =
16+
fromStr && toStr && typeof fromStr === "string" && typeof toStr === "string"
17+
? {
18+
from: ignoreTime(new Date(fromStr)),
19+
to: ignoreTime(new Date(toStr)),
20+
type: "custom",
21+
}
22+
: {
23+
from: ignoreTime(defaultRange.from),
24+
to: ignoreTime(defaultRange.to),
25+
type: defaultRange.type,
26+
};
27+
28+
return range;
29+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import { QueryClient } from "@tanstack/react-query";
4+
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
5+
import type {
6+
PersistedClient,
7+
Persister,
8+
} from "@tanstack/react-query-persist-client";
9+
import { del, get, set } from "idb-keyval";
10+
11+
const queryClient = new QueryClient({
12+
defaultOptions: {
13+
queries: {
14+
// 1 hour
15+
gcTime: 3600 * 1000,
16+
},
17+
},
18+
});
19+
20+
/**
21+
* Creates an Indexed DB persister
22+
* @see https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
23+
*/
24+
export function createIDBPersister(idbValidKey: IDBValidKey = "reactQuery") {
25+
return {
26+
persistClient: async (client: PersistedClient) => {
27+
await set(idbValidKey, client);
28+
},
29+
restoreClient: async () => {
30+
return await get<PersistedClient>(idbValidKey);
31+
},
32+
removeClient: async () => {
33+
await del(idbValidKey);
34+
},
35+
} satisfies Persister;
36+
}
37+
38+
export const idbPersister = createIDBPersister();
39+
40+
export function IdbPersistProvider(props: {
41+
children?: React.ReactNode;
42+
}) {
43+
return (
44+
<PersistQueryClientProvider
45+
client={queryClient}
46+
persistOptions={{ persister: idbPersister }}
47+
>
48+
{props.children}
49+
</PersistQueryClientProvider>
50+
);
51+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { pageProcessing } from "../_common/pageProcessing";
2+
import { ClientPage } from "../client/client-page";
3+
import { IdbPersistProvider } from "./idb-persister";
4+
5+
export default async function Page(props: {
6+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
7+
}) {
8+
const range = await pageProcessing(props);
9+
10+
return (
11+
<IdbPersistProvider>
12+
<ClientPage range={range} />
13+
</IdbPersistProvider>
14+
);
15+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"use client";
2+
3+
import { useQuery } from "@tanstack/react-query";
4+
import { useState } from "react";
5+
import {
6+
DateRangeSelector,
7+
type Range,
8+
} from "../../../../../../../components/analytics/date-range-selector";
9+
import { ChartUI } from "../_common/ChartUI";
10+
import { fetchTestData } from "../_common/fetchTestData";
11+
12+
export function ClientPage(props: {
13+
range: Range;
14+
}) {
15+
const [range, setRange] = useState<Range>(props.range);
16+
const chartDataQuery = useQuery({
17+
queryKey: [
18+
"fetchTestDate",
19+
{
20+
from: range.from.toISOString(),
21+
to: range.to.toISOString(),
22+
},
23+
],
24+
queryFn: async () => {
25+
return fetchTestData(range);
26+
},
27+
staleTime: 500 * 1000,
28+
refetchOnMount: false,
29+
refetchOnWindowFocus: false,
30+
});
31+
32+
console.log("ClientPage", {
33+
from: range.from.toISOString(),
34+
to: range.to.toISOString(),
35+
});
36+
37+
return (
38+
<div>
39+
<DateRangeSelector
40+
range={range}
41+
setRange={(v) => {
42+
setRange(v);
43+
// update search params without reloading the page
44+
window.history.pushState(
45+
{},
46+
"",
47+
`?from=${v.from.toISOString()}&to=${v.to.toISOString()}`,
48+
);
49+
}}
50+
/>
51+
<div className="h-8" />
52+
<ChartUI
53+
data={chartDataQuery.data || []}
54+
isPending={chartDataQuery.isPending}
55+
title="Client Component"
56+
/>
57+
</div>
58+
);
59+
}

0 commit comments

Comments
 (0)