背景
TanStack Routerを使っていると、useQueryClient やその他のグローバルな依存関係をコンポーネント内でどう扱うべきか迷いました。
そのため、今回は自身が調べたことから、「なぜグローバルなフックではなく Router Context を使うべきなのか」 という理由と、ファイルを分割した際に陥りやすい 「循環参照(Circular Dependency)」 の解決策をメモ書きします。
1. 基本:Contextから依存関係を取得する
まず、QueryClient などをコンポーネントで使う際、TanStack Routerでは以下のような書き方が推奨されます。
✅ 良い例(Route Contextから取得する)
// Component.tsx
import { Route } from './routes/my-route'
function MyComponent() {
// ✅ Route Context経由で注入されたインスタンスを使う
const { queryClient } = Route.useRouteContext()
// ...
}
❌ ダメな例(グローバルフックを直接使う)
// Component.tsx
import { useQueryClient } from '@tanstack/react-query'
function MyComponent() {
// ⚠️ コンポーネント内では動くが、Loaderとの整合性が取れない
const queryClient = useQueryClient()
// ...
}
なぜ Context 経由が良いのか?
最大の理由は loader との一貫性 です。
TanStack Routerの loader 関数は、コンポーネントがレンダリングされる 前 に実行されるため、Reactフック(useQueryClientなど)を使うことができません。そのため、loader 内では Context 経由で queryClient を受け取る必要があります(Dependency Injection)。
export const Route = createFileRoute('/posts')({
// loaderでは引数のcontextから受け取る
loader: ({ context: { queryClient } }) => {
return queryClient.ensureQueryData(...)
},
component: MyComponent,
})
コンポーネント側でも同じ context を参照することで、「データ取得」と「描画」で同じインスタンス・同じデータソースを確実に共有できます。
2. 落とし穴:ファイルの分割と循環参照
開発が進むと、「Route定義」と「コンポーネント」を別ファイルに分けたくなることがあります。しかし、ここで不用意に Route をインポートするとエラーになります。
💀 循環参照が起きるパターン
routes/posts.tsx
import { PostsComponent } from './PostsComponent' // 1. コンポーネントをインポート
export const Route = createFileRoute('/posts')({
component: PostsComponent,
})
PostsComponent.tsx
// 2. Contextを使うためにRouteをインポートしたい...
import { Route } from './routes/posts'
export function PostsComponent() {
// 💥 ここで実行時エラー!
// Routeはまだ初期化されていないため undefined になりクラッシュする
const context = Route.useRouteContext()
return <div>...</div>
}
これが 循環参照(Circular Dependency) です。
-
routes/posts.tsxを読むにはPostsComponentが必要 -
PostsComponentを読むにはroutes/posts.tsx(Route) が必要
という「卵と鶏」の状態になり、解決できずにエラーになります。
解決策:getRouteApi を使う
ファイルを分割しつつ、型安全に Context を利用するには、getRouteApi を使います。これは Route オブジェクトそのものをインポートするのではなく、パスID(文字列)を使ってAPIを取得する方法です。
PostsComponent.tsx
import { getRouteApi } from '@tanstack/react-router'
// 👇 パスIDを指定してAPIを取得(Routeオブジェクトのインポートは不要!)
const routeApi = getRouteApi('/posts')
export function PostsComponent() {
// ✅ これで循環参照せずに、型安全なContextが使える
const { queryClient } = routeApi.useRouteContext()
return <div>Success!</div>
}
これなら、コンポーネント側からRouteファイルへの物理的な依存(import)がなくなるため、循環参照の問題がきれいに解決します。
まとめ
-
Contextを使う:
loaderとコンポーネントでデータの出所を統一するため、queryClient等は Router Context から取得する。 -
型安全にする:
Route.useRouteContext()またはfrom指定ありのフックを使い、厳密な型推論の恩恵を受ける。 -
循環参照を避ける: コンポーネントを別ファイルにする場合は、
RouteをインポートせずgetRouteApi('/path')を使う。
TanStack Router は型安全性が強力な分、こうした依存関係の設計も重要になることがわかりました。