Skip to content

Instantly share code, notes, and snippets.

@merthanmerter
Last active October 27, 2024 17:08
Show Gist options
  • Select an option

  • Save merthanmerter/09f15240175132e68a4775e14be3144f to your computer and use it in GitHub Desktop.

Select an option

Save merthanmerter/09f15240175132e68a4775e14be3144f to your computer and use it in GitHub Desktop.
tanstack router - stateful loader data params
export type RootContext = {
store: JotaiStore;
};
export const Route = createRootRouteWithContext<RootContext>()({
component: Root,
});
export default function Root() {
return <Outlet />;
}
export const Route = createFileRoute("/hub/")({
loader: async ({ context }) => {
const { year, month } = context.store.get(postReportAtom);
return await context.proxy.posts.chart.query({
year,
month,
});
},
component: Page,
});
export default function Page() {
return (
<React.Fragment>
<h1 className='font-bold text-xl mb-4'>Hub Dashboard</h1>
<div className='grid lg:grid-cols-[3fr_1fr] gap-4 items-start'>
<BarChartComponent />
<ActiveUsersComponent />
</div>
</React.Fragment>
);
}
const chartConfig = {
total: {
label: "Total",
color: "hsl(var(--chart-1))",
},
} satisfies ChartConfig;
const getChartData = (
data: /* this is how we infer types from loader data */ typeof Route.types.loaderData,
) => {
return data?.map((item) => ({
day: new Date(item.day).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
}),
total: item.total,
}));
};
function BarChartComponent() {
const router = useRouter();
const data = Route.useLoaderData();
const total = Number(data?.reduce((acc, curr) => acc + +curr.total, 0));
/**
* Demonstrates how to send custom
* parameters to the loader function.
*
* - `postReportAtom` is subscribed to the store
* to track state changes.
* - Subscription happens in: `./src/store/index.ts`
* - Example use case: Instead of using query parameters,
* this approach allows stateful updates for
* tracking `month` and `year` data.
* In this example, we don't want to show user
* the query parameters in the URL and provide a
* global state management.
*
* @file './src/store/post-report.ts'
*/
const [{ month, year }, setDate] = useAtom(postReportAtom);
const onMonthChange = (newMonth: Date) => {
setDate({
month: newMonth.getMonth() + 1,
year: newMonth.getFullYear(),
});
router.invalidate();
};
return (
<Card>
<CardHeader>
<div className='flex items-center justify-between gap-2'>
<div>
<CardTitle className='flex items-center gap-2'>
Total Posts
</CardTitle>
<CardDescription>Total number of posts created</CardDescription>
</div>
<MonthPicker
currentMonth={new Date(year, month - 1, 1)}
onMonthChange={onMonthChange}
/>
</div>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<BarChart
accessibilityLayer
data={getChartData(data)}
margin={{
top: 20,
}}>
<CartesianGrid vertical={false} />
<XAxis
dataKey='day'
tickLine={false}
tickMargin={10}
axisLine={false}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar
dataKey='total'
fill='var(--color-total)'
radius={8}>
<LabelList
position='top'
offset={12}
className='fill-foreground'
fontSize={12}
/>
</Bar>
</BarChart>
</ChartContainer>
</CardContent>
<CardFooter className='flex-col items-start gap-2 text-sm'>
<div className='flex gap-2 font-medium leading-none'>
Total {total} {total > 1 ? "posts" : "post"} created this month.{" "}
<TrendingUpIcon className='size-4' />
</div>
<div className='leading-none text-muted-foreground'>
Showing data for {month} / {year}
</div>
</CardFooter>
</Card>
);
}
/**
* Register the router instance for type safety
*/
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
/**
* Router setup and context
*/
const router = createRouter({
routeTree,
context: {
store, // jotai store
},
});
const rootElement = document.getElementById("root")!;
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<JotaiProvider store={store}>
{/* other providers */}
<RouterProvider router={router} />
</JotaiProvider>
</StrictMode>,
);
}
import { atom } from "jotai/vanilla";
/**
* Defines `postReportAtom`, an atom that manages
* custom parameters (year and month) for use in
* the loader function.
*
* - The atom is subscribed to the store in:
* `./src/store/index.ts`
* - Defaults: current year and month
* (e.g., `{ year: 2024, month: 10 }`)
*
* @file './src/store/index.ts'
*/
export const postReportAtom = atom<{
year: number;
month: number;
}>({
year: new Date().getFullYear(),
month: new Date().getMonth() + 1,
});
import { createStore } from "jotai/vanilla";
import { postReportAtom } from "./post-report";
const store = createStore();
store.sub(postReportAtom, () => {});
export default store;
export type JotaiStore = typeof store;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment