Type inference doesn't seem to work with context and loader functions
Hiya, I'm getting the types of context and location inferred as {} in the following spot:
main.tsx looks like this:
I'm not sure what I'm doing wrong here.
import { createFileRoute } from '@tanstack/react-router'
import NewsFeed from '../components/NewsFeed'
import { storiesQueryOptions } from '../queries'
type NewsSearchParams = {
page?: number
}
export const Route = createFileRoute('/news')({
validateSearch: (search: Record<string, unknown>): NewsSearchParams => {
// validate and parse the search params into a typed state
return {
page: Number(search?.page ?? 1),
}
},
loader: async ({ context, location }) => {
// inference seems to fail here
// @ts-expect-error Not sure why we are getting type errors here, it has something to do with the router module declaration
await context.queryClient.ensureQueryData(storiesQueryOptions(location.search.page ?? 1))
},
component: News,
})
function News() {
return (
<NewsFeed />
)
}
import { createFileRoute } from '@tanstack/react-router'
import NewsFeed from '../components/NewsFeed'
import { storiesQueryOptions } from '../queries'
type NewsSearchParams = {
page?: number
}
export const Route = createFileRoute('/news')({
validateSearch: (search: Record<string, unknown>): NewsSearchParams => {
// validate and parse the search params into a typed state
return {
page: Number(search?.page ?? 1),
}
},
loader: async ({ context, location }) => {
// inference seems to fail here
// @ts-expect-error Not sure why we are getting type errors here, it has something to do with the router module declaration
await context.queryClient.ensureQueryData(storiesQueryOptions(location.search.page ?? 1))
},
component: News,
})
function News() {
return (
<NewsFeed />
)
}
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import './index.css'
import { routeTree } from './routeTree.gen'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false, // default: true
},
},
})
const router = createRouter({
routeTree,
context: {
queryClient,
},
defaultPreload: 'intent',
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</StrictMode>,
)
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import './index.css'
import { routeTree } from './routeTree.gen'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false, // default: true
},
},
})
const router = createRouter({
routeTree,
context: {
queryClient,
},
defaultPreload: 'intent',
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</StrictMode>,
)
3 Replies
like-goldOP•2w ago
The source is here: https://github.com/opspch/hn-frontend
GitHub
GitHub - opspch/hn-frontend: HN clone in React
HN clone in React. Contribute to opspch/hn-frontend development by creating an account on GitHub.
quickest-silver•2w ago
import { createFileRoute } from '@tanstack/react-router'
import NewsFeed from '../components/NewsFeed'
import { storiesQueryOptions } from '../queries'
type NewsSearchParams = {
page?: number
}
export const Route = createFileRoute('/news')({
validateSearch: (search): NewsSearchParams => {
return {
page: Number(search?.page ?? 1),
}
},
loaderDeps: ({ search }) => ({ search }),
loader: async ({ context, deps }) => {
await context.queryClient.ensureQueryData(storiesQueryOptions(deps.search.page))
},
})
import { createFileRoute } from '@tanstack/react-router'
import NewsFeed from '../components/NewsFeed'
import { storiesQueryOptions } from '../queries'
type NewsSearchParams = {
page?: number
}
export const Route = createFileRoute('/news')({
validateSearch: (search): NewsSearchParams => {
return {
page: Number(search?.page ?? 1),
}
},
loaderDeps: ({ search }) => ({ search }),
loader: async ({ context, deps }) => {
await context.queryClient.ensureQueryData(storiesQueryOptions(deps.search.page))
},
})
location doesn't include type information, so you need to access it from the loader deps
https://tanstack.com/router/v1/docs/framework/react/guide/data-loading#using-loaderdeps-to-access-search-params
As for the context, can you confirm that you have used createRootRouteWithContext when defining your root route? Typically it would be like
type RouteContext = {
/**
* The applications query client
*/
queryClient: QueryClient,
}
export const Route = createRootRouteWithContext<RouteContext>()({
context: {
queryClient: undefined!
}
});
type RouteContext = {
/**
* The applications query client
*/
queryClient: QueryClient,
}
export const Route = createRootRouteWithContext<RouteContext>()({
context: {
queryClient: undefined!
}
});
like-goldOP•2w ago
I wasn't using
createRootRouteWithContext, so that did the trick. However, the typechecker complained when I tried putting the context in the arguments, so I just left it as it was before, with component: RootLayout.
The loaderDeps fix worked too.
Here's the final code:
src/routes/__root.tsx
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import type { QueryClient } from '@tanstack/react-query';
import hnLogo from '../../y18.svg';
const RootLayout = () => (
<div className='m-2 bg-beige border-b-2 border-orange'>
<div className="flex gap-2 text-base bg-orange p-0.5">
<Link to="/"><img className='border-1 border-white' src={hnLogo} alt='Hacker News logo'></img></Link>
<Link to="/news" className="font-bold">
Hacker News
</Link>
<Link to="/about">
about
</Link>
</div>
<Outlet />
<TanStackRouterDevtools initialIsOpen={false} position="bottom-right" />
</div>
)
interface RouteContext {
queryClient: QueryClient,
}
export const Route = createRootRouteWithContext<RouteContext>()({
component: RootLayout
});
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import type { QueryClient } from '@tanstack/react-query';
import hnLogo from '../../y18.svg';
const RootLayout = () => (
<div className='m-2 bg-beige border-b-2 border-orange'>
<div className="flex gap-2 text-base bg-orange p-0.5">
<Link to="/"><img className='border-1 border-white' src={hnLogo} alt='Hacker News logo'></img></Link>
<Link to="/news" className="font-bold">
Hacker News
</Link>
<Link to="/about">
about
</Link>
</div>
<Outlet />
<TanStackRouterDevtools initialIsOpen={false} position="bottom-right" />
</div>
)
interface RouteContext {
queryClient: QueryClient,
}
export const Route = createRootRouteWithContext<RouteContext>()({
component: RootLayout
});
src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import './index.css'
import { routeTree } from './routeTree.gen'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false, // default: true
},
},
})
const router = createRouter({
routeTree,
context: {
queryClient,
},
defaultPreload: 'intent',
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</StrictMode>,
)
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import './index.css'
import { routeTree } from './routeTree.gen'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false, // default: true
},
},
})
const router = createRouter({
routeTree,
context: {
queryClient,
},
defaultPreload: 'intent',
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</StrictMode>,
)
src/routes/news.tsx
import { createFileRoute } from '@tanstack/react-router'
import NewsFeed from '../components/NewsFeed'
import { storiesQueryOptions } from '../queries'
type NewsSearchParams = {
page?: number
}
export const Route = createFileRoute('/news')({
validateSearch: (search: Record<string, unknown>): NewsSearchParams => {
// validate and parse the search params into a typed state
return {
page: Number(search?.page ?? 1),
}
},
loaderDeps: ({ search }) => ({ search }),
loader: async ({ context, deps }) => {
await context.queryClient.ensureQueryData(storiesQueryOptions(deps.search.page ?? 1))
},
component: News,
})
function News() {
return (
<NewsFeed />
)
}
import { createFileRoute } from '@tanstack/react-router'
import NewsFeed from '../components/NewsFeed'
import { storiesQueryOptions } from '../queries'
type NewsSearchParams = {
page?: number
}
export const Route = createFileRoute('/news')({
validateSearch: (search: Record<string, unknown>): NewsSearchParams => {
// validate and parse the search params into a typed state
return {
page: Number(search?.page ?? 1),
}
},
loaderDeps: ({ search }) => ({ search }),
loader: async ({ context, deps }) => {
await context.queryClient.ensureQueryData(storiesQueryOptions(deps.search.page ?? 1))
},
component: News,
})
function News() {
return (
<NewsFeed />
)
}