T
TanStack2w ago
like-gold

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:
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 />
)
}
main.tsx looks like this:
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>,
)
I'm not sure what I'm doing wrong here.
3 Replies
like-gold
like-goldOP2w ago
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
quickest-silver2w 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))
},
})
I imagine the issue is that 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!
}
});
Here is some documentation on this: https://tanstack.com/router/latest/docs/framework/react/guide/router-context Note: you can get syntax highlighting for code blocks in discord by using ```ts or ```tsx
like-gold
like-goldOP2w 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 />
)
}

Did you find this page helpful?