Skip to content

Commit 7dba2ec

Browse files
committed
[Experiment] Chart fetching strategies
1 parent fb4d0a2 commit 7dba2ec

File tree

26 files changed

+1107
-165
lines changed

26 files changed

+1107
-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: 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+
}
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+
}

0 commit comments

Comments
 (0)