Skip to content

Commit 68f0c42

Browse files
committed
[Experiment] Chart fetching strategies
1 parent fb4d0a2 commit 68f0c42

File tree

24 files changed

+882
-165
lines changed

24 files changed

+882
-165
lines changed

apps/dashboard/package.json

Lines changed: 3 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",
@@ -89,6 +91,7 @@
8991
"react-table": "^7.8.0",
9092
"recharts": "2.14.1",
9193
"remark-gfm": "^4.0.0",
94+
"responsive-rsc": "^0.0.5",
9295
"server-only": "^0.0.1",
9396
"shiki": "1.27.0",
9497
"sonner": "^1.7.1",

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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
isPending: boolean;
8+
}) {
9+
return (
10+
<ThirdwebBarChart
11+
title="Test"
12+
data={props.data}
13+
config={{
14+
count: {
15+
label: "Foo",
16+
color: "hsl(var(--chart-1))",
17+
},
18+
}}
19+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
20+
isPending={props.isPending}
21+
/>
22+
);
23+
}
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(1, 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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { addDays, differenceInCalendarDays } from "date-fns";
2+
import { simulateChartFetchingDelay } from "./delays";
3+
4+
export type TestData = Array<{ time: Date; count: number }>;
5+
6+
export async function fetchTestData(params: {
7+
from: Date;
8+
to: Date;
9+
}) {
10+
await simulateChartFetchingDelay();
11+
12+
const days = differenceInCalendarDays(params.to, params.from);
13+
14+
const data: TestData = [];
15+
for (let i = 0; i < days; i++) {
16+
data.push({
17+
time: addDays(params.from, i),
18+
count: ((i + 1) % 10) + i,
19+
});
20+
}
21+
22+
return data;
23+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import "server-only";
2+
3+
import { unstable_cache } from "next/cache";
4+
import { fetchTestData } from "./fetchTestData";
5+
6+
export const getCachedFetchTestData = unstable_cache(
7+
fetchTestData,
8+
["fetchTestData"],
9+
{
10+
revalidate: 3600, // 1 hour
11+
},
12+
);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
5+
export function getRange(params: {
6+
from: string | undefined;
7+
to: string | undefined;
8+
}) {
9+
const fromStr = params.from;
10+
const toStr = params.to;
11+
12+
const defaultRange = getLastNDaysRange("last-30");
13+
const range: Range =
14+
fromStr && toStr && typeof fromStr === "string" && typeof toStr === "string"
15+
? {
16+
from: ignoreTime(new Date(fromStr)),
17+
to: ignoreTime(new Date(toStr)),
18+
type: "custom",
19+
}
20+
: {
21+
from: ignoreTime(defaultRange.from),
22+
to: ignoreTime(defaultRange.to),
23+
type: defaultRange.type,
24+
};
25+
26+
return range;
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { simulatePageProcessingDelay } from "./delays";
2+
import { getRange } from "./getRange";
3+
4+
export async function pageProcessing(props: {
5+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
6+
}) {
7+
const searchParams = await props.searchParams;
8+
const fromStr = searchParams.from;
9+
const toStr = searchParams.to;
10+
11+
await simulatePageProcessingDelay();
12+
13+
const range = getRange({
14+
from: typeof fromStr === "string" ? fromStr : undefined,
15+
to: typeof toStr === "string" ? toStr : undefined,
16+
});
17+
18+
return range;
19+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use client";
2+
3+
import { useQuery } from "@tanstack/react-query";
4+
import { useRef } from "react";
5+
import { DateRangeSelector } from "../../../../../../../components/analytics/date-range-selector";
6+
import { ChartUI } from "../_common/ChartUI";
7+
import { type TestData, fetchTestData } from "../_common/fetchTestData";
8+
import { useRange, useSetRange } from "./contexts";
9+
10+
export function QueryChartUI(props: {
11+
initialData: TestData;
12+
}) {
13+
const range = useRange();
14+
const initialRange = useRef(range);
15+
const chartDataQuery = useQuery({
16+
queryKey: [
17+
"fetchTestDate",
18+
{
19+
from: range.from.toDateString(),
20+
to: range.to.toDateString(),
21+
},
22+
],
23+
queryFn: () => {
24+
console.log("client side query", range);
25+
return fetchTestData(range);
26+
},
27+
initialData() {
28+
const hasInitialData =
29+
range.from.toString() === initialRange.current.from.toString() &&
30+
range.to.toString() === initialRange.current.to.toString();
31+
32+
if (hasInitialData) {
33+
return props.initialData;
34+
}
35+
},
36+
refetchOnMount: false,
37+
refetchOnWindowFocus: false,
38+
});
39+
40+
return (
41+
<ChartUI
42+
data={chartDataQuery.data || []}
43+
isPending={chartDataQuery.isPending}
44+
/>
45+
);
46+
}
47+
48+
export function RangeSelector() {
49+
const range = useRange();
50+
const setRange = useSetRange();
51+
return (
52+
<DateRangeSelector
53+
range={range}
54+
setRange={(v) => {
55+
setRange(v);
56+
// update search params without reloading the page
57+
const searchParams = new URLSearchParams(window.location.search);
58+
searchParams.set("from", v.from.toDateString());
59+
searchParams.set("to", v.to.toDateString());
60+
window.history.pushState({}, "", `?${searchParams.toString()}`);
61+
}}
62+
/>
63+
);
64+
}

0 commit comments

Comments
 (0)