Tanstack Router For React - A Complete Guide
Published: 2024-06-19 05:38:18


by [Wisdom Ekpotu](https://blog.openreplay.com/authors/wisdom-ekpotu)



<blockquote><em>
Tanstack Router provides an easy, safe way to define routes for your React web site, and is worth a look, as this article shows.
</em></blockquote>




<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>





 [Tanstack Router](https://tanstack.com/router/v1) is a modern and scalable routing solution for React, created by *Tanner Linsey(creator of react-query)*. Its [core objective](https://tanstack.com/router/v1/docs/framework/react/decisions-on-dx#1-why-is-the-routers-configuration-done-this-way) centers around type-safety and developer productivity.

The core philosophy behind TanStack Router is simply the full utilization of Typescript for web routing. Developers should be able to write type-safe routes, actions, and loaders, which will result in fewer runtime errors. This provides a cohesive environment where routes are well-defined type-safe contracts instead of just navigation pathways in an application.

On the contrary, React Router is built on the philosophy of simplicity and flexibility. It aims to make routing implementation in React applications simple. Unlike TanStack Router, React Router takes an unopinionated and incremental adoption approach, allowing developers to start simple and gradually add more advanced routing techniques as the need arises.

The following table from the [TanStack Router documentation](https://tanstack.com/router/latest/docs/framework/react/comparison) compares TanStack Router and React Router:



Feature/Capability Key:

- ✅ 1st-class, built-in, and ready to use with no added configuration or code
- 🔵 Supported via add-on package
- 🟡 Partial Support
- 🔶 Possible, but requires custom code/implementation/casting
- 🛑 Not officially supported

| | TanStack Router | React Router DOM [_(Website)_][router] | Next.JS [_(Website)_][nextjs] |
| ---------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------- | ----------------------------------------------------- |
| History, Memory & Hash Routers | ✅ | ✅ | 🛑 |
| Nested / Layout Routes | ✅ | ✅ | ✅ |
| Suspense-like Route Transitions | ✅ | ✅ | ✅ |
| Typesafe Routes | ✅ | 🛑 | 🟡 |
| Code-based Routes | ✅ | ✅ | 🛑 |
| File-based Routes | ✅ | ✅ | ✅ |
| Router Loaders | ✅ | ✅ | ✅ |
| SWR Loader Caching | ✅ | 🛑 | ✅ |
| Route Prefetching | ✅ | ✅ | ✅ |
| Auto Route Prefetching | ✅ | 🔵 (via Remix) | ✅ |
| Route Prefetching Delay | ✅ | 🔶 | 🛑 |
| Path Params | ✅ | ✅ | ✅ |
| Typesafe Path Params | ✅ | 🛑 | 🛑 |
| Path Param Validation | ✅ | 🛑 | 🛑 |
| Custom Path Param Parsing/Serialization | ✅ | 🛑 | 🛑 |
| Ranked Routes | ✅ | ✅ | ✅ |
| Active Link Customization | ✅ | ✅ | ✅ |
| Optimistic UI | ✅ | ✅ | 🔶 |
| Typesafe Absolute + Relative Navigation | ✅ | 🛑 | 🛑 |
| Route Mount/Transition/Unmount Events | ✅ | 🛑 | 🛑 |
| Devtools | ✅ | 🛑 | 🛑 |
| Basic Search Params | ✅ | ✅ | ✅ |
| Search Param Hooks | ✅ | ✅ | ✅ |
| `<Link/>`/`useNavigate` Search Param API | ✅ | 🟡 (search-string only via the `to`/`search` options) | 🟡 (search-string only via the `to`/`search` options) |
| JSON Search Params | ✅ | 🔶 | 🔶 |
| TypeSafe Search Params | ✅ | 🛑 | 🛑 |
| Search Param Schema Validation | ✅ | 🛑 | 🛑 |
| Search Param Immutability + Structural Sharing | ✅ | 🔶 | 🛑 |
| Custom Search Param parsing/serialization | ✅ | 🔶 | 🛑 |
| Search Param Middleware | ✅ | 🛑 | 🛑 |
| Suspense Route Elements | ✅ | ✅ | ✅ |
| Route Error Elements | ✅ | ✅ | ✅ |
| Route Pending Elements | ✅ | ✅ | ✅ |
| `<Block>`/`useBlocker` | ✅ | 🔶 | ❓ |
| SSR | ✅ | ✅ | ✅ |
| Streaming SSR | ✅ | ✅ | ✅ |
| Deferred Primitives | ✅ | ✅ | ✅ |
| Navigation Scroll Restoration | ✅ | ✅ | ❓ |
| Loader Caching (SWR + Invalidation) | 🔶 (TanStack Query is recommended) | 🛑 | ✅ |
| Actions | 🔶 (TanStack Query is recommended) | ✅ | ✅ |
| `<Form>` API | 🛑 | ✅ | ✅ |
| Full-Stack APIs | 🛑 | ✅ | ✅ |

[bp-tanstack-router]: https://badgen.net/bundlephobia/minzip/@tanstack/react-router
[bpl-tanstack-router]: https://bundlephobia.com/result?p=@tanstack/react-router
[gh-tanstack-router]: https://github.com/tanstack/router
[stars-tanstack-router]: https://img.shields.io/github/stars/tanstack/router?label=%F0%9F%8C%9F
[_]: _
[router]: https://github.com/remix-run/react-router
[bp-router]: https://badgen.net/bundlephobia/minzip/react-router-dom
[gh-router]: https://github.com/remix-run/react-router
[stars-router]: https://img.shields.io/github/stars/remix-run/react-router?label=%F0%9F%8C%9F
[bpl-router]: https://bundlephobia.com/result?p=react-router-dom
[bpl-history]: https://bundlephobia.com/result?p=history
[_]: _
[nextjs]: https://nextjs.org/docs/routing/introduction
[bp-nextjs]: https://badgen.net/bundlephobia/minzip/next.js?label=All
[gh-nextjs]: https://github.com/vercel/next.js
[stars-nextjs]: https://img.shields.io/github/stars/vercel/next.js?label=%F0%9F%8C%9F
[bpl-nextjs]: https://bundlephobia.com/result?p=next


<div>
 <center> <small><i>Comparison Table</i> <br/> credit: <a style="color:#333; text-decoration: underline" href='https://tanstack.com/router/latest/docs/framework/react/comparison'>TanStack Router Documentation</a></small></center>
</div>

## Build a Single Page Application with React and TanStack Router


In this section, we will build a single-page application in React that retrieves data from an API and runs in the browser. Routing will be done using TanStack Router, and Tailwind-CSS will be used for styling. The application you'll build will show information about popular movies via the [TMDb API](https://developer.themoviedb.org/reference/search-movie). 

Building this project from scratch will help us understand how to use the TanStack Router from a hands-on perspective, which is crucial when learning a new technology.



### Prerequisite
- Working knowledge of React and Typescript
- Familiarity with [Tailwind CSS](https://tailwindcss.com/)
- Code Editor — such as VSCode
- [Node.js Latest LTS](https://nodejs.org/en) installed on your computer.
- TMDb [API key](https://developer.themoviedb.org/reference/search-movie)
 
The complete code for this project can be found on *[GitHub](https://github.com/wisdomekpotu/tanstack-movieapp)*.

### Project Demo
![bandicam2024-04-2013-25-27-366-ezgif.com-optimize](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image1.gif)

### Creating a Vite App and Installing Dependencies
We will use [vite](https://vitejs.dev/) to create our React app.

```bash
npm create vite@latest movie-app --template react-ts

npm install
```

The preceding command allows you to scaffold a react project with Typescript using Vite. We use Typescript because our routing will be done via Tanstack Router, which is *100% type-safe*.

Next, install the following dependencies; they will be utilized for the project.


```bash
npm install -D zod tailwindcss postcss autoprefixer

npx tailwindcss init -p
```

In the preceding command, you installed [zod](https://zod.dev/?id=introduction), [tailwindcss](https://tailwindcss.com/docs/guides/vite) and its peer dependencies. The `npx tailwindcss init -p` command generates the `tailwind.config.js` and `postcss.config.js` files, which will be used to configure tailwind. Also, the *zod* library will validate and infer Typescript type(s) for routes.

After running the above commands, the following `package.json` will be created for the project.




```json
{
 "name": "movie-app",
 "private": true,
 "version": "0.0.0",
 "type": "module",
 "scripts": {
 "dev": "vite",
 "build": "tsc && vite build",
 "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
 "preview": "vite preview"
 },
 "dependencies": {
 "react": "^18.2.0",
 "react-dom": "^18.2.0"
 },
 "devDependencies": {
 "@types/react": "^18.2.66",
 "@types/react-dom": "^18.2.22",
 "@typescript-eslint/eslint-plugin": "^7.2.0",
 "@typescript-eslint/parser": "^7.2.0",
 "@vitejs/plugin-react": "^4.2.1",
 "autoprefixer": "^10.4.19",
 "eslint": "^8.57.0",
 "eslint-plugin-react-hooks": "^4.6.0",
 "eslint-plugin-react-refresh": "^0.4.6",
 "postcss": "^8.4.38",
 "tailwindcss": "^3.4.3",
 "typescript": "^5.2.2",
 "vite": "^5.2.0",
 "zod": "^3.22.4"
 }
}
```

Now, let's configure tailwind via the `tailwind.config.js` and `index.css` files, respectively.

Enter the following code:

`tailwind.config.js`:

```javascript
/** @type {import('tailwindcss').Config} */
export default {
 content: [
 "./index.html",
 "./src/**/*.{js,ts,jsx,tsx}",
 ],
 theme: {
 extend: {},
 },
 plugins: [],
}
```

`src\index.css`:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### Tanstack Router Setup
The development environment for our project is ready. In this section, we will install and configure TanStack Router, which is the main focus of this project. Let's start by installing the TanStack Router library. 

Enter the following commands:

```bash
npm install @tanstack/react-router

npm install --save-dev @tanstack/react-router-vite-plugin
```

The `@tanstack/react-router-vite-plugin` will regenerate the routes whenever our application compiles.

Next, we set up Vite to use the plugin we just installed.

Enter the following code in `vite.config.ts`:
```typescript
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
 plugins: [react(), TanStackRouterVite()],
});
```


### The Routes Directory
All route implementations for our application are carried out in the' routes' directory.

In the source[`src`] directory, create a new subdirectory called `routes`. This directory will contain all the routes of our application. Tanstack Router will infer routes based on its folder structure via filenames.

Next, inside the `routes` directory create the files `__root.tsx` and `index.tsx` respectively.

```
📦 movie-app
┣ 📂 src
┃ ┣ 📂 routes
 ┃ ┣ 📄 __root.tsx
 ┃ ┣ 📄 index.tsx
```

Next, run the following command:
```bash
npm run dev
```

The preceding command will generate a *[routeTree](https://tanstack.com/router/latest/docs/framework/react/guide/creating-a-router#route-tree)* for our application located in `routeTree.gen.ts` inside the `src` directory.

```typescript
// src\routeTree.gen.ts
import { Route as rootRoute } from "./routes/__root";
import { Route as IndexImport } from "./routes/index";

// Create/Update Routes
const IndexRoute = IndexImport.update({
 path: "/",
 getParentRoute: () => rootRoute,
} as any);

// Populate the FileRoutesByPath interface
declare module "@tanstack/react-router" {
 interface FileRoutesByPath {
 "/": {
 preLoaderRoute: typeof IndexImport;
 parentRoute: typeof rootRoute;
 };
 }
}

// Create and export the route tree
export const routeTree = rootRoute.addChildren([IndexRoute]);
```

### Router Instance
The router instance is the mechanism that connects TanStack Router to our React application—just like `<BrowserRouter>` [from React Router](https://reactrouter.com/en/main/router-components/browser-router).

Enter the following code in `App.tsx`:

```typescript
// src\App.tsx
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";

const router = createRouter({ routeTree });

declare module "@tanstack/react-router" {
 interface Register {
 router: typeof router;
 }
}

function App() {
 return <RouterProvider router={router} />;
}

export default App;
```

The preceding code creates a router instance via the `createRouter` function from TanStack Router, which ensures all declared routes are *100% type-safe*.

### Layout Route
The layout route is the parent container of all routes in our application. 

Enter the following code in `__root.tsx`:

```typescript
// src\routes\__root.tsx
import { createRootRoute, Outlet, Link } from '@tanstack/react-router';

export const Route = createRootRoute({
 component: LayoutComponent,
});

function LayoutComponent() {
 return (
 <html lang='en'>
 <head>
 <meta charSet='UTF-8' />
 <meta name='viewport' content='width=device-width, initial-scale=1.0' />
 <title>Movie-App</title>
 </head>
 <body className='bg-black max-w-4xl mx-auto text-white px-5 md:px-0'>
 <header className='flex justify-between items-center p-4 bg-[#780909] text-white rounded-b-2xl shadow-xl shadow-[#df0707] mb-6'>
 <h1 className='text-2xl flex'>
 <Link
 to='/'
 search={{ page: 1 }}
 activeProps={{
 className: 'font-bold hello',
 }}
 activeOptions={{
 includeSearch: false,
 }}
 >
 Movies🍿
 </Link>
 <div className='mx-5'>|</div>
 <Link
 to='/search'
 search={{ q: '' }}
 activeProps={{
 className: 'font-bold',
 }}
 >
 Search
 </Link>
 </h1>
 <div id='favorites-count'>{/* <FavoritesCount /> */}</div>
 </header>
 <Outlet /> {/* Start rendering router matches */}
 </body>
 </html>
 );
}
```

The preceding code does the following:
- Creates a root route component(via `createRootRoute`) that will be displayed on every application page.
- Implements basic navigation for our application via the `Link` component for TanStack Router.
- Renders other paths that the router will match via the `<Outlet/>` component from TanStack Router.
- Finally, applies styles in markup via tailwind classes.

Next, if the `npm run dev` command is still running, you should get the following output in your browser.

Browser Output:

![screenshot](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image2.png)

### Building the Movies Index-Page
In this section, we will build the index page of our app, where all the movies retrieved from an API will be displayed in a paginated view.

*N/B: We'll use the [TMDb REST API](https://developer.themoviedb.org/docs/getting-started), which provides information about various popular TV shows and movies. Get an API key.* 

When you ran the `npm run dev` command after setting up TanStack Router, along with the *routeTree* being generated, placeholder code was also generated for each file in the `routes` directory. So, your `index.tsx` page should look like this:

```typescript
// src\routes\index.tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/')({
 component: () => <div>Hello/!</div>,
});
```
In the preceding code, we have a basic route implementation with TanStack Router. Unlike other file-based routers where components are exported, we export the *file route* instead. We create the file route via *[createFileRoute function](https://tanstack.com/router/latest/docs/framework/react/api/router/createFileRouteFunction#createfileroute-function)*, which accepts a single argument of type `string` that represents the *path* of the file that the route will be inferred from. Finally, we pass in a `component` that will be rendered when we hit the route path. 

### Pagination
Let's implement the pagination feature to allow us to view data retrieved from the API in paginated segments.

First, we will create the pagination component.
In the `src` directory create a subdirectory called `components` and then create a `paging.tsx` file.

Enter the following code in `paging.tsx`
```typescript
import React from 'react';
import { Link } from '@tanstack/react-router';

export default function Paging({
 pages,
 Route,
 page,
}: {
 pages: number;
 Route: any;
 page: number;
}) {
 return (
 <div className='flex gap-1 text-xl font-bold justify-end'>
 {new Array(pages).fill(0).map((_, i) =>
 page === i + 1 ? (
 <div className='px-4 py-2 border border-red-300 rounded bg-[#0b0000] text-white'>
 {i + 1}
 </div>
 ) : (
 <Link
 key={i}
 from={Route.id}
 search={{
 page: i + 1,
 }}
 className='px-4 py-2 border border-red-300 rounded hover:bg-[#a33d3da1]'
 >
 {i + 1}
 </Link>
 )
 )}
 </div>
 );
}
```
In the preceding code, we have a basic react presentational component. The pagination logic and conditional rendering based on the logic are in the' return' block of the component.

The pagination logic is basically:
- A new array is created with its length set to the number of pages that will be paginated via the `pages` prop.
- Each index in the array is *filled* with`0`— `array.fill(0)`
- The looped through via the `map()` function, where we check if the current page we have in our search param(via `page` prop) equals the index of the current array item.
- Next, a presentational component is shown depending on the result of the preceding condition.
- The *[Link](https://tanstack.com/router/latest/docs/framework/react/api/router/linkComponent#link-props)* component is used to navigate to a new page if the conditional is `true`—i.e, the search param(`page`) equals the current index(`i`). Check the docs for prop options([from](https://tanstack.com/router/latest/docs/framework/react/api/router/PathParamOptionsType), [search](https://tanstack.com/router/latest/docs/framework/react/api/router/SearchParamOptionsType)) used in the Link component.
- Finally, markup is styled with tailwind.


Next, we will build the index route, where movies will be retrieved from the API and paginated.

Enter the following code in `index.tsx`:

```typescript
// src\routes\index.tsx
import { createFileRoute, Link } from "@tanstack/react-router";
import { z } from "zod";
import Paging from "../components/Paging";

export const Route = createFileRoute("/")({
 component: IndexComponent,
 validateSearch: z.object({
 page: z.number().catch(1),
 }),
});

function IndexComponent() {
 const pages = 4;
 const { page } = Route.useSearch();
 return (
 <div>
 <div className="flex justify-end pr-5 py-5">
 <Paging page={page} pages={pages} Route={Route} />
 </div>
 </div>
 );
}
```
The preceding code does the following:
- The *zod* library is used to manage and type our search parameters. 
- The `validateSearch` option in `createFileRoute`is used to validate our search parameters from the URL via the zod library. In this case `page` must be `number` with a default value of `1`.
- The `IndexComponent` is a component that will be rendered when the route path is matched.
- In `IndexComponent`, *[useSearch hook](https://tanstack.com/router/latest/docs/framework/react/api/router/useSearchHook)* is used to access the current value of the `page` search parameter.
- The `pages` variable represents the number of pages for pagination.
- Required props are passed to the `Paging` component.

**Browser output:**

![pagination](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image3.png)

### Retrieving Data
We will be retrieving the data for our application from the [TMDb API](https://developer.themoviedb.org/reference/intro/getting-started) which will require an API key, get it [here](https://www.themoviedb.org/settings/api).

Next, create an `api.ts` file in the `src` directory and enter the following code:

```typescript
// get all Movies
export async function getMovies(page: number = 1) {
 const response = await fetch(
 `https://api.themoviedb.org/3/movie/popular?include_adult=false&language=en-US&page=${encodeURIComponent(page)}&api_key=${API_KEY}`
 )
 .then((r) => r.json())
 .then((r) => ({
 pages: 4,
 movies: r.results,
 }));
 return response;
}
```
In the preceding code, the exported `getMovies()` function is used to fetch data from the API. It accepts a `page` parameter from the URL search parameters. Finally, it returns an object, where the `pages` property represents the number of pages, and `movies` is the data gotten from the API to be displayed on the UI.

Now, in `index.tsx`, enter the following code:

```typescript
// src\routes\index.tsx
import { createFileRoute, Link } from "@tanstack/react-router";
import Paging from "../components/Paging";
import { getMovies } from "../api";
import { z } from "zod";

export const Route = createFileRoute("/")({
 component: IndexComponent,
 validateSearch: z.object({
 page: z.number().catch(1),
 }),
 loaderDeps: ({ search: { page } }) => ({ page }),
 loader: ({ deps: { page } }) => getMovies(page),
});

function IndexComponent() {
 const { page } = Route.useSearch();
 const { movies, pages } = Route.useLoaderData();

 return (
 <div>
 <div className="flex justify-end pr-5 py-5">
 <Paging page={page} pages={pages} Route={Route} />
 </div>
 <div>{JSON.stringify(movies, null, 2)}</div>
 </div>
 );
}
```

In the preceding code, note the following:
- The `getMovies()` is imported and used. It gives us access to data from the API.
- The `/` route support pagination via the search param `page`. For this data to be stored in a cache, it has to be accessed via the `loaderDeps` function.
- The `loader` gets the `page` search param stored in cache and uses it to get data from the API via the `getMovies()` function.
- `Route.useLoaderData()` gives us access to the data loaded in the [loader](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#route-loaders). 
- Finally, `JSON.stringify()` is used to show the data on the UI.



Browser Output:

![screenshot](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image4.png)

Now, let's create a presentational component to make our UI more presentable:

In the `components` directory create `MovieCards.tsx` file, and add the following code:

```typescript
import { Link } from '@tanstack/react-router';

export default function IndexComponent({ movies }: { movies: any[] }) {
 return (
 <div className='grid grid-cols-1 md:grid-cols-2'>
 {movies.map((m, i) => (
 <Link
 to='/movies/$movieId'
 params={{
 movieId: m.id,
 }}
 className='flex m-2'
 key={m.id || i}
 >
 <img
 src={`https://image.tmdb.org/t/p/w500${m.poster_path}`}
 className='rounded-tl-lg rounded-bl-lg aspect-w-5 aspect-h-7 w-1/4'
 />
 <div className='w-3/4 flex flex-col'>
 <div className='font-bold text-xl px-4 bg-[#ba0c0c] text-white py-2 rounded-tr-md'>
 {m.original_title}
 </div>
 <div className='border-red-900 border-b-2 border-r-2 rounded-br-lg flex-grow pt-3'>
 <div className='italic line-clamp-2 px-4'>{m.overview}</div>
 <div className='flex justify-between px-4 pt-3 items-center'>
 {/* <FavoriteButton movieId={m.id} /> */}
 <div>{m.vote_average.toFixed(1)} out of 10</div>
 </div>
 </div>
 </div>
 </Link>
 ))}
 </div>
 );
}
```

>N/B: The path `/movies/$movieId` in the `to` property in `Link` is not yet defined, so you will see an error. We will fix it in the next section.

Next, we will import this component into `index.tsx`, where it will replace `JSON.stringify()`, which is currently being used.

```typescript
.....
import MovieCards from "../components/MovieCards";

....
function IndexComponent() {
 const { page } = Route.useSearch();
 const { movies, pages } = Route.useLoaderData();

 return (
 <div>
 <div className="flex justify-end pr-5 py-5">
 <Paging page={page} pages={pages} Route={Route} />
 </div>
 <MovieCards movies={movies} />
 </div>
 );
}
```

Browser Output:

![screenshot](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image5.png)

Our app's UI looks better now.

### Building The Movie Detail Page
The movie detail page displays the details of a single movie when it is clicked on.

### Using Path Parameters

*According to the [Tanstack Official Docs](https://tanstack.com/router/latest/docs/framework/react/guide/path-params#path-params):*
> *Path params are used to match a single segment (the text until the next `/`) and provide its value back to you as a named variable. They are defined by using the `$` character prefix in the path, followed by the key variable to assign it to.*


To implement the movie details functionality, we will use *[path parameters](https://tanstack.com/router/latest/docs/framework/react/guide/path-params#path-params)*, this will allow us to define dynamic routes via each movie `id`.

In the `routes` directory, create a new `movies` subdirectory, in the `movies` directory create a `$movieId.tsx`. This file will contain the logic for fetching an individual movie from TMDb API.

Enter the following code in `$movieId.tsx`:

```typescript
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/movies/$movieId")({
 component: MovieDetail,
});

function MovieDetail() {
 return <h1>Movie!!!</h1>;
}
```
The preceding code is a basic implementation of the movie detail functionality without data from an API. The `MovieDetail` component is rendered when the path matches `/movies/$movieId`, where `$movieId` stands for the `id` of the movie that is clicked on the index page. 

Browser Output:

![bandicam2024-04-2314-09-32-372-ezgif.com-video-to-gif-converter](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image6.gif)

Now, let's create a component for the movie details.

In the `component` directory, create a `Movie.tsx` file, and add the following code:

```typescript
import type { Movie } from '../types';

export default function Movie({ movie }: { movie: Movie }) {
 return (
 <div className='flex'>
 <div className='flex-shrink w-1/4'>
 <img
 src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}
 className='aspect-w-5 aspect-h-7 rounded-3xl'
 />
 </div>
 <div className='w-3/4'>
 <div className='font-bold text-2xl px-4'>{movie.title}</div>
 <div className='italic text-xl px-4 mb-5'>{movie.tagline}</div>
 <div className='pt-3 px-4'>
 <div className='italic'>{movie.overview}</div>
 <div className='flex justify-between pt-3 items-center'>
 <div>{movie.vote_average.toFixed(1)} out of 10</div>
 </div>
 <div className='grid grid-cols-[30%_70%] pt-3 gap-3'>
 <div className='font-bold text-right'>Runtime</div>
 <div>{movie.runtime} minutes</div>

 <div className='font-bold text-right'>Genres</div>
 <div>{movie.genres.map(({ name }) => name).join(', ')}</div>

 <div className='font-bold text-right'>Release Date</div>
 <div>{movie.release_date}</div>

 <div className='font-bold text-right'>Production Companies</div>
 <div>
 {movie.production_companies.map(({ name }) => name).join(', ')}
 </div>

 <div className='font-bold text-right'>Languages</div>
 <div>
 {movie.spoken_languages
 .map(({ english_name }) => english_name)
 .join(', ')}
 </div>
 </div>
 </div>
 </div>
 </div>
 );
}
```


Now, let's get the movie details from the API.

Enter the following code in the `api.ts` file.

```typescript
....
// get Movie by Id
export async function getMovie(id: string) {
 const response = await fetch(
 `https://api.themoviedb.org/3/movie/${id}?language=en-US&api_key=${API_KEY}`
 ).then((r) => r.json());

 return response;
}
```
The preceding code is a fetch request that retrieves details for individual movies based on their `id`.


Now, enter the following code in `$movieId.tsx`:
```typescript
import { createFileRoute } from "@tanstack/react-router";
import { getMovie } from "../../api";
import Movie from "../../components/Movie";

export const Route = createFileRoute("/movies/$movieId")({
 component: MovieDetail,
 loader: ({ params: { movieId } }) => getMovie(movieId),
});

function MovieDetail() {
 const movie = Route.useLoaderData();
 return <Movie movie={movie} />;
}
```
The above code does the following:
- The `getMovie()` function is imported from `api.tsx`.
- No need for `loaderDeps`, because we are working with path params. We simply pass the parameter into `loader`.
- `useLoaderData()` is used to access data from the `loader`.
- The `<Movie/>` component is used to display the movie detail on the UI.

Browser Output:

![bandicam2024-04-2316-41-44-656-ezgif.com-video-to-gif-converter](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image7.gif)

### Building Movie Search
The search feature will enable us to search for specific movies in our app. So, we will build a *search route* to implement this feature.

### Search Params for State Management
We will be storing our search query in the URL via search params, the code below shows how this is done with Tanstack Router.

In the `routes` directory, create a `search.tsx` file. Add the following code:

```typescript
import { createFileRoute, useNavigate} from "@tanstack/react-router";
import { useState } from "react";

interface SearchParams {
 query: string;
}

export const Route = createFileRoute("/search")({
 component: SearchRoute,
 validateSearch: (search: { query: string }): SearchParams => {
 return {
 query: (search.query as string) || "",
 };
 },
});

function SearchRoute() {
 const { query } = Route.useSearch();
 const navigate = useNavigate({ from: Route.id });
 const [newQuery, setNewQuery] = useState(query);

 return (
 <div className="p-2">
 <div className="flex gap-2">
 <input
 value={newQuery}
 onChange={(e) => {
 setNewQuery(e.target.value);
 }}
 onKeyUp={(e) => {
 if (e.key === "Enter") {
 navigate({
 search: (old: { query: string }) => ({
 ...old,
 query: newQuery,
 }),
 });
 }
 }}
 className="border-2 border-gray-300 rounded-md p-1 text-black w-full"
 />
 <button
 onClick={() => {
 navigate({
 search: (old: { query: string }) => ({
 ...old,
 q: newQuery,
 }),
 });
 }}
 >
 Search
 </button>
 </div>
 // Results
 </div>
 );
}
```
The preceding code does the following:
- We define the `/search` route with the `createFileRoute()` function from TanStack Router.
- The [`validateSearch`](https://tanstack.com/router/latest/docs/framework/react/guide/search-params#validating-search-params) option is used to validate the search params of the `/search` route, it also returns a typed `SearchParams` object with a query property set to `string`.
- The `SearchRoute` component accesses the search param via `Route.useSearch()`.
- We use the `useNavigate` function from [TanStack Router](https://tanstack.com/router/latest/docs/framework/react/guide/navigation#usenavigate) to programmatically navigate from the `/search` route —`useNavigate({ from: Route.id })`.[]
- The `useState` hook for React is used to update the state of the `SearchRoute` component based on the search param `query`.
- In the `return` block, we update the search param based on the `input` `value` typed in by the user as a search query. Also, the `useNavigate` function is used to update *search* string(`query`) to what is currently being typed by the user via the [SearchParamOptions type](https://tanstack.com/router/latest/docs/framework/react/api/router/SearchParamOptionsType)


Browser Output:

![bandicam2024-04-2412-33-31-989-ezgif.com-video-to-gif-converter](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image8.gif)

In the above demo, you will notice that the search param in the URL got updated to the query that was entered in the search box.

### Showing Search Results
With the search functionality in place, the next step is to display the search result(s). For this, we will create a *nested route* inside the search route, which will also serve as an *[index route](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#index-routes)* of the search route.

### Nested Routing
The *[Outlet Componet](https://tanstack.com/router/latest/docs/framework/react/api/router/outletComponent#outlet-component)* is used to create nested routes in TanStack Router. Since the search route will be the parent route of the nested route, the outlet component is used here:
```typescript
// src\routes\search.tsx
import { createFileRoute, useNavigate, Outlet } from "@tanstack/react-router";
....

function SearchRoute() {
 ....
 return (
 <div className="p-2">
 <div className="flex gap-2">
 .....
 </div>
 <Outlet />
 </div>
 );
}
```
In the preceding code, the `<Outlet/>` component was added to the `SearchRoute` component in `search.tsx`. This is where the result(s) for our search query will be displayed.

First, let's retrieve the search results data from the API. Add the following code in `api.ts`

```typescript
......
// Search Movie
export async function searchMovie(query: string = "") {
 const response = await fetch(
 `https://api.themoviedb.org/3/search/movie?query=${encodeURIComponent(
 query
 )}&include_adult=false&language=en-US&page=1&api_key=${API_KEY}`
 )
 .then((r) => r.json())
 .then((r) => r.results);
 return response;
}
```
In the preceding code is the logic for retrieving search results from the TMDb API.


Next, in the `routes` directory, create the `search.index.tsx` file, and add the following code:

```typescript
import { createFileRoute } from "@tanstack/react-router";
import MovieCards from "../components/MovieCards";
import { searchMovie } from "../api";

interface SearchParams {
 query: string;
}

export const Route = createFileRoute("/search/")({
 component: SearchRoute,
 loaderDeps: ({ search: { query } }) => ({ query }),
 loader: async ({ deps: { query } }) => {
 const searched_movies = await searchMovie(query);
 return {
 searched_movies,
 };
 },
 validateSearch: (search: { query: string }): SearchParams => {
 return {
 query: (search.query as string) || "",
 };
 },
});

function SearchRoute() {
 const { searched_movies } = Route.useLoaderData();

 return (
 <>
 <MovieCards movies={searched_movies || []} />
 </>
 );
}
```
In the preceding code:
- We defined the `/search/` file route, which is the index route of the search route `/search`
- `validateSearch` is used to get the query, which will enable the interior index(`/search/`) to be routed into the `<Outlet/>` of the parent route(`/search`).
- The `searchMovie()` function retrieves movie results from the API via the `query` parameter.
- `loaderDeps` caches the search query, which is then used by the `loader` to fetch the search results.
- Finally, `useLoaderData` allows the search results to be accessed by the `SearchRoute` component. `SearchRoute` renders the results on the UI via the `<MovieCards/>` component.


Browser Output:

![bandicam2024-04-2414-40-16-751-ezgif.com-video-to-gif-converter](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/image9.gif)

### Streaming Search Results

In this section, we are going to add some lazy-loading and code-splitting to our application.

We will display details for the first movie returned from the search results at the top of the page, the [*defer function*](https://tanstack.com/router/latest/docs/framework/react/api/router/deferFunction#:~:text=The%20defer%20function%20wraps%20a,promise%20is%20resolved%20or%20rejected.) and *[Await component](https://tanstack.com/router/latest/docs/framework/react/api/router/awaitComponent#await-component)* from TanStack Router will be used to enable other search results to be displayed sooner, without waiting for the details of the first movie to be rendered.

Sample UI:


![sample](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/imagea.png)

### Fallbacks with React Suspense

Enter the following code in `search.index.tsx`:

```typescript
import { createFileRoute, defer, Await } from "@tanstack/react-router";
import { Suspense } from "react";

import MovieCards from "../components/MovieCards";
import Movie from "../components/Movie";

import { searchMovie, getMovie } from "../api";

interface SearchParams {
 query: string;
}

export const Route = createFileRoute("/search/")({
 component: SearchRoute,
 loaderDeps: ({ search: { query } }) => ({ query }),
 loader: async ({ deps: { query } }) => {
 const searched_movies = await searchMovie(query);
 return {
 searched_movies,
 firstMovie: searched_movies?.[0]?.id
 ? defer(getMovie(searched_movies[0].id))
 : null,
 };
 },
 validateSearch: (search: { query: string }): SearchParams => {
 return {
 query: (search.query as string) || "",
 };
 },
});

function SearchRoute() {
 const { searched_movies, firstMovie } = Route.useLoaderData();
 
// fallbacks with React Suspense
 return (
 <>
 {firstMovie && (
 <div className="my-5">
 <Suspense fallback={<div>Loading...</div>}>
 <Await promise={firstMovie}>
 {(movie) => {
 return <Movie movie={movie} />;
 }}
 </Await>
 </Suspense>
 </div>
 )}
 <MovieCards movies={searched_movies || []} />
 </>
 );
}
```
In the preceding code:
- `getMovie()` calls the same API endpoint as movie details page in `$movieId.tsx`
- In `loader`, the data(first movie details) from `getMovie()` is *deferred* if the data is available via the [*defer function*](https://tanstack.com/router/latest/docs/framework/react/api/router/deferFunction#:~:text=The%20defer%20function%20wraps%20a,promise%20is%20resolved%20or%20rejected.). 
- `defer()` allows us to lazy-load the data(first movie details), in case the API endpoint for the movie details takes time, we can show other search results before the first movie details.
- In the `return` block of the `SearchRoute` component:
 - `Suspense` is used to provide a *fallback* until the promise(first movie details) from the [`Await`](https://tanstack.com/router/latest/docs/framework/react/api/router/awaitComponent#await-component) component is resolved.
 - The *first movie details* is rendered via `<Movie/>` component to the UI.

Browser Output:

![bandicam2024-04-2415-53-47-513-ezgif.com-optimize](https://blog.openreplay.com/images/tanstack-router-for-react--a-complete-guide/images/imageb.gif)

## Summary
In this article, you built a single-page movie application that lets you view movies, search for movies, and get the details of individual movies with React and TanStack Router. You learned how to use some basic and advanced features of TanStack Router like typesafe routes and links, nested layouts, advanced data loader capabilities, search params as a React State replacement, and integration with React Suspense.