Last active
October 27, 2024 17:08
-
-
Save merthanmerter/09f15240175132e68a4775e14be3144f to your computer and use it in GitHub Desktop.
tanstack router - stateful loader data params
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export type RootContext = { | |
| store: JotaiStore; | |
| }; | |
| export const Route = createRootRouteWithContext<RootContext>()({ | |
| component: Root, | |
| }); | |
| export default function Root() { | |
| return <Outlet />; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | |
| ); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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>, | |
| ); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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