0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【TanStack Router】useRouteContextの正しい使い方と循環参照の回避

0
Posted at

背景

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) です。

  1. routes/posts.tsx を読むには PostsComponent が必要
  2. 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)がなくなるため、循環参照の問題がきれいに解決します。

まとめ

  1. Contextを使う: loader とコンポーネントでデータの出所を統一するため、queryClient 等は Router Context から取得する。
  2. 型安全にする: Route.useRouteContext() または from 指定ありのフックを使い、厳密な型推論の恩恵を受ける。
  3. 循環参照を避ける: コンポーネントを別ファイルにする場合は、Route をインポートせず getRouteApi('/path') を使う。

TanStack Router は型安全性が強力な分、こうした依存関係の設計も重要になることがわかりました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?