Skip to content

Commit 8a9062c

Browse files
committed
[Experiment] Chart fetching strategies
1 parent fb4d0a2 commit 8a9062c

File tree

21 files changed

+750
-165
lines changed

21 files changed

+750
-165
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: 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: 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-30");
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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 <DateRangeSelector range={range} setRange={setRange} />;
52+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use client";
2+
3+
import { createContext, useContext, useState } from "react";
4+
import invariant from "tiny-invariant";
5+
import type { Range } from "../../../../../../../components/analytics/date-range-selector";
6+
7+
type SetRange = (range: Range) => void;
8+
9+
// eslint-disable-next-line no-restricted-syntax
10+
const RangeCtx = createContext<Range | null>(null);
11+
// eslint-disable-next-line no-restricted-syntax
12+
const SetRangeCtx = createContext<SetRange | null>(null);
13+
14+
export function RangeProvider(props: {
15+
value: Range;
16+
children: React.ReactNode;
17+
}) {
18+
const [range, setRange] = useState<Range>(props.value);
19+
20+
return (
21+
<RangeCtx.Provider value={range}>
22+
<SetRangeCtx.Provider value={setRange}>
23+
{props.children}
24+
</SetRangeCtx.Provider>
25+
</RangeCtx.Provider>
26+
);
27+
}
28+
29+
export function useRange() {
30+
const range = useContext(RangeCtx);
31+
invariant(range, "Not in RangeProvider");
32+
return range;
33+
}
34+
35+
export function useSetRange() {
36+
const setRange = useContext(SetRangeCtx);
37+
invariant(setRange, "Not in RangeProvider");
38+
return setRange;
39+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { pageProcessing } from "../_common/pageProcessing";
2+
import { RangeSelector } from "./client";
3+
import { RangeProvider } from "./contexts";
4+
import { RSCQueryChart } from "./server";
5+
6+
export default async function Page(props: {
7+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
8+
}) {
9+
const range = await pageProcessing(props);
10+
11+
return (
12+
<RangeProvider value={range}>
13+
<div>
14+
<RangeSelector />
15+
<div className="h-8" />
16+
<RSCQueryChart
17+
range={{
18+
from: range.from,
19+
to: range.to,
20+
}}
21+
/>
22+
</div>
23+
</RangeProvider>
24+
);
25+
}

0 commit comments

Comments
 (0)