T
TanStack6mo ago
exotic-emerald

Client side routing via react-query

It might be a controversial idea. But after initial loading I wont to run all next loader requests (that happens during soft-navigation) with react-query. Right now, I achieved that behavior with next code
import React from 'react';
import { createFileRoute, ErrorComponent } from '@tanstack/react-router'

import { HomePage } from '@/pages/HomePage';
import { homePageQueryOptions, useHomePageData } from '@/utils/posts/useHomePageData';

export const Route = createFileRoute('/')({
loader: async ({ context, cause }) => {
if (cause !== 'preload' || typeof window !== 'undefined') {
return;
}
await context.queryClient.ensureQueryData(homePageQueryOptions())
},
component: HomePageComponent,
errorComponent: ErrorComponent,
})

function HomePageComponent() {
const { error } = useHomePageData();

if (error) {
return <ErrorComponent error={error} />
}

return (
<HomePage />
)
}
import React from 'react';
import { createFileRoute, ErrorComponent } from '@tanstack/react-router'

import { HomePage } from '@/pages/HomePage';
import { homePageQueryOptions, useHomePageData } from '@/utils/posts/useHomePageData';

export const Route = createFileRoute('/')({
loader: async ({ context, cause }) => {
if (cause !== 'preload' || typeof window !== 'undefined') {
return;
}
await context.queryClient.ensureQueryData(homePageQueryOptions())
},
component: HomePageComponent,
errorComponent: ErrorComponent,
})

function HomePageComponent() {
const { error } = useHomePageData();

if (error) {
return <ErrorComponent error={error} />
}

return (
<HomePage />
)
}
I wrapped every page with such structure. Can we have usePageData<T> hook for loading loader data from client side via react-query?
4 Replies
exotic-emerald
exotic-emeraldOP6mo ago
@Manuel Schiller sorry for ping, but what do you think about that approach? Maybe here's a better way? Source code: https://github.com/akhmadshin/tanstack-optimistic-navigation-starter
GitHub
GitHub - akhmadshin/tanstack-optimistic-navigation-starter
Contribute to akhmadshin/tanstack-optimistic-navigation-starter development by creating an account on GitHub.
fair-rose
fair-rose6mo ago
ping again in a few days please afk for a few days
Can we have usePageData<T> hook for loading loader data from client side via react-query?
build it yourself then? or what do you need for that
exotic-emerald
exotic-emeraldOP6mo ago
@Manuel Schiller Maybe usePageData<T> hook is a bad idea, because you need to manually set type every time you call it. The one thing that concerns me is amount of required boilerplate code in my approach. I improved it a little by creating WithErrorHandler component:
import { ParentComponent } from '@/types/general';
import React from 'react';
import { ErrorRouteComponent, NotFoundRouteComponent } from '@tanstack/react-router';

interface Props {
notFoundComponent?: NotFoundRouteComponent;
errorComponent?: ErrorRouteComponent | false | null;
error: Error & { isNotFound: boolean } | null;
}
export const WithErrorHandler: ParentComponent<Props> = ({ errorComponent: ErrorComponent, notFoundComponent: NotFoundComponent, error, children }) => {
if (error) {
if (error.isNotFound) {
return NotFoundComponent ? <NotFoundComponent data={{}} /> : null;
}
return ErrorComponent ? <ErrorComponent reset={() => {}} error={error} /> : null;
}
return children
}
import { ParentComponent } from '@/types/general';
import React from 'react';
import { ErrorRouteComponent, NotFoundRouteComponent } from '@tanstack/react-router';

interface Props {
notFoundComponent?: NotFoundRouteComponent;
errorComponent?: ErrorRouteComponent | false | null;
error: Error & { isNotFound: boolean } | null;
}
export const WithErrorHandler: ParentComponent<Props> = ({ errorComponent: ErrorComponent, notFoundComponent: NotFoundComponent, error, children }) => {
if (error) {
if (error.isNotFound) {
return NotFoundComponent ? <NotFoundComponent data={{}} /> : null;
}
return ErrorComponent ? <ErrorComponent reset={() => {}} error={error} /> : null;
}
return children
}
Route code:
import { ErrorComponent, createFileRoute } from '@tanstack/react-router'
import { NotFound } from '@/components/NotFound'
import React from 'react'
import { BlogItemPage } from '@/pages/BlogItemPage';
import { blogItemPageOptions, useBlogItemPageData } from '@/utils/posts/useBlogItemPageData';
import { WithErrorHandler } from '@/components/WithErrorHandler';

const NotFoundRouteComponent = () => <NotFound>Post not found</NotFound>

export const Route = createFileRoute('/blog/$postId')({
loader: async ({ params: { postId }, context, cause }) => {
if (cause !== 'preload' || typeof window !== 'undefined') {
return;
}
const data = await context.queryClient.ensureQueryData(
blogItemPageOptions(postId),
)
return {
title: data.attributes.title,
}
},
errorComponent: ErrorComponent,
notFoundComponent: NotFoundRouteComponent,
component: PostComponent,
});

function PostComponent() {
const { error } = useBlogItemPageData(Route);

return (
<WithErrorHandler
notFoundComponent={Route.options.notFoundComponent}
errorComponent={Route.options.errorComponent}
error={error}
>
<BlogItemPage />
</WithErrorHandler>
)
}
import { ErrorComponent, createFileRoute } from '@tanstack/react-router'
import { NotFound } from '@/components/NotFound'
import React from 'react'
import { BlogItemPage } from '@/pages/BlogItemPage';
import { blogItemPageOptions, useBlogItemPageData } from '@/utils/posts/useBlogItemPageData';
import { WithErrorHandler } from '@/components/WithErrorHandler';

const NotFoundRouteComponent = () => <NotFound>Post not found</NotFound>

export const Route = createFileRoute('/blog/$postId')({
loader: async ({ params: { postId }, context, cause }) => {
if (cause !== 'preload' || typeof window !== 'undefined') {
return;
}
const data = await context.queryClient.ensureQueryData(
blogItemPageOptions(postId),
)
return {
title: data.attributes.title,
}
},
errorComponent: ErrorComponent,
notFoundComponent: NotFoundRouteComponent,
component: PostComponent,
});

function PostComponent() {
const { error } = useBlogItemPageData(Route);

return (
<WithErrorHandler
notFoundComponent={Route.options.notFoundComponent}
errorComponent={Route.options.errorComponent}
error={error}
>
<BlogItemPage />
</WithErrorHandler>
)
}
fair-rose
fair-rose6mo ago
I hope that someday we can allow users to provide their own abstractions on top of createFileRoute. but for now... that's not possible.

Did you find this page helpful?