If you are a beginner in the React library and want to create a Single-Page Application, then your first choice would be to use a React Router library to enable routing in your application, and there is nothing wrong with that.
React router is probably everyone’s first solution for implementing routing. However, it is not efficient for managing the state of the data while navigating to different routes.
React router lacks integrated data fetching, which leads to the creation of separate data and state handling mechanisms. This is where TanStack Router comes into play.
What is a TanStack Router?
TanStack Router is a robust routing library from the TanStack suite of apps that offers features and capabilities, including integrated data fetching, that go beyond what is typically available in other routing libraries like React Router.
Key Features
First-Class Data Management Integration
TanStack router efficiently integrates with TanStack Query for integrated data fetching, which helps us predictably manage the state of our application.
We have already written an article on integrating TanStack Router with TanStack Query. With the help of TanStack Query, you can efficiently fetch, cache, prune, and sync the data.
Nested and Parallel Routes
If you work with complex web applications, you need to implement nested and parallel routes to handle more complex state management. The TanStack router efficiently manages everything, making it easier to handle more sophisticated UI layouts and data-fetching scenarios.
TypeScript Support
The core feature that sparked my curiosity is that using TanStack Router, your routes are automatically typesafe, ensuring a better developer experience when working with routes and route data.
Route Loaders and Actions
For a more integrated approach to data fetching and route handling, TanStack Routing allows you to define loaders and actions directly on routes.
Project Overview
Our post’s primary goal is to teach how to set up TanStack Router in React to create a simple single-page application in which we navigate different routes while fetching data from a demo API server.
To achieve these, we will create a simple React Project with Vite bundler and install Material-UI for styling, TanStack Query for fetching, caching, and state management, and TanStack Router to enable routing in our application.
Here is the step-by-step guide:
Step 1: Setup a React Project with Vite
Type the below command to use Vite’s React template to create a new project:
npm create vite@latest tanstack-router-app -- --template react
Go inside the project folder and install the dependencies defined in the package.json file:
cd tanstack-router-app npm install
Now, install the MUI, Axios, TanStack Query, and TanStack Router using the command below:
npm install axios @tanstack/react-router @tanstack/react-query @mui/material @emotion/react @emotion/styled
Step 2: Setup a Routing using TanStack Router
To efficiently manage the routing in our application, create a separate file called router.js under the src folder and add the below code:
// src/router.js import { createRootRoute, createRoute, createRouter } from '@tanstack/react-router'; import App from './App'; import Home from './components/Home'; import Posts from './components/Posts'; import PostDetails from './components/PostDetails'; // Create a root route const rootRoute = createRootRoute({ component: App, }); // Create routes const homeRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', component: Home, }); const postsRoute = createRoute({ getParentRoute: () => rootRoute, path: 'posts', component: Posts, }); const postDetailsRoute = createRoute({ getParentRoute: () => rootRoute, path: 'posts/$id', component: PostDetails, }); // Create the route tree const routeTree = rootRoute.addChildren([ homeRoute, postsRoute, postDetailsRoute, ]); // Create the router const router = createRouter({ routeTree }); export default router;
The TanStack Router provides mainly three functions:
- createRootRoute(): This function creates the root route of the application.
-
createRoute(): It creates individual application routes (child routes).
-
createRouter(): It creates the router instance.
What we did here is create a root route for our application and then define child routes.
Now, we have a parent (root) route and child routes, and we need to create a route Tree that attaches all the child routes to the root route.
The rootRoute.addChildren() function creates a route Tree that defines the complete route mechanism for our application.
The last step is to create a router instance while passing the route tree, and we will use this instance on our src/main.jsx file to register.
You can see that the App component would be our root route, and Posts, Home, and PostDetails are child components that we have yet to create.
Step 3: Create a custom hook to fetch the data
For this project, we will use the JSONPlaceHolder API to fetch the “posts” and “posts/id” data.
We will use the TanStack Query to fetch and cache the data and display the data through components.
Under the “src” folder, create a new folder called “hooks”, and in that folder, create a new hook file called usePosts.js and add the code below:
// src/hooks/usePosts.js import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; const fetchPosts = async () => { const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts'); return data; }; const fetchPost = async ({ queryKey }) => { const [, postId] = queryKey; const { data } = await axios.get(`https://jsonplaceholder.typicode.com/posts/${postId}`); return data; }; export const usePosts = () => useQuery({ queryKey: ['posts'], queryFn: fetchPosts, }); export const usePost = (postId) => useQuery({ queryKey: ['post', postId], queryFn: () => fetchPost({ queryKey: ['post', postId] }), enabled: !!postId, });
In this code, we performed the side effect operation using the axios.get() function to send GET request to API and fetch the data. Then, we used the useQuery hook to cache the data.
Step 4: Create components
Under the “src” folder, create a new folder called “components”, and inside that folder, create the following four components:
- Home.jsx
- Posts.jsx
- PostList.jsx
- PostDetails.jsx
Write the following code in the src/components/Home.jsx file:
// src/components/Home.jsx import { Container, Typography } from '@mui/material'; const Home = () => ( <Container> <Typography variant="h3" component="h1" gutterBottom> Welcome to My App </Typography> <Typography variant="body1"> This is the home page. </Typography> </Container> ); export default Home;
Write the following code in the src/components/PostList.jsx file:
// src/components/PostList.jsx import { List, ListItem, ListItemText } from '@mui/material'; import { Link } from '@tanstack/react-router'; const PostList = ({ posts }) => ( <List> {posts.map(post => ( <ListItem key={post.id}> <ListItemText> <Link to={`/posts/${post.id}`} style={{ textDecoration: 'none' }}> {post.title} </Link> </ListItemText> </ListItem> ))} </List> ); export default PostList;
Write the following code in the src/components/PostDetails.jsx file:
// src/components/PostDetails.jsx import { Container, Typography, CircularProgress } from '@mui/material'; import { useParams } from '@tanstack/react-router'; import { usePost } from '../hooks/usePosts'; const PostDetails = () => { const { id } = useParams({ from: '/posts/$id' }); const { data, error, isLoading } = usePost(id); if (isLoading) return <CircularProgress />; if (error) return <Typography color="error">An error occurred: {error.message}</Typography>; return ( <Container> <Typography variant="h4" component="h1" gutterBottom> {data?.title} </Typography> <Typography variant="body1">{data?.body}</Typography> </Container> ); }; export default PostDetails;
Write the following code in the src/components/Posts.jsx file:
// src/components/Posts.jsx import { Container, Typography, CircularProgress } from '@mui/material'; import { usePosts } from '../hooks/usePosts'; import PostList from './PostList'; const Posts = () => { const { data, error, isLoading } = usePosts(); if (isLoading) return <CircularProgress />; if (error) return <Typography color="error">An error occurred: {error.message}</Typography>; return ( <Container> <Typography variant="h3" component="h1" gutterBottom> Posts </Typography> <PostList posts={data} /> </Container> ); }; export default Posts;
Step 5: Create an App Component
Update the src/App.jsx file:
// src/App.jsx import { Container } from '@mui/material'; import { Outlet } from '@tanstack/react-router'; const App = () => ( <Container component="main"> <Outlet /> </Container> ); export default App;
Step 6: Set up the main entry point
Update the src/main.jsx file like this:
// src/main.jsx import React from 'react'; import ReactDOM from 'react-dom'; import { RouterProvider } from '@tanstack/react-router'; import CssBaseline from '@mui/material/CssBaseline'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import router from './router'; const queryClient = new QueryClient(); ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <CssBaseline /> <QueryClientProvider client={queryClient}> <RouterProvider router={router} /> </QueryClientProvider> </React.StrictMode> );
In the above code, you can see the <RouterProvider router={router} /> component, which is a key part of integrating the TanStack Router with your React application.
Step 7: Run the project
Go to the terminal and start the development server using the command below:
npm run dev
Go to this URL: http://localhost:5173/posts/
The posts in the above image are clickable, and if you click on any post, it will redirect to a specific detail page like this:
And here is the home page whose URL is this: http://localhost:5173/
That’s all!
Here is the complete code uploaded on Github.