TanStack Query (formerly known as React Query) is used to fetch, cache, and synchronize server state in React applications.
TanStack Router helps you manage your web application’s navigation, allowing you to define routes and handle URL changes.
Integrating TanStack Query with TanStack Router in a React application has the following advantages:
- It can efficiently and automatically fetch and cache the data.
- It provides seamless navigation and state management to the React application.
- It simplifies the process and improves user experience with loading states and error handling.
- Code management becomes straightforward and less error-prone with a separation of concerns.
- You can use the various hooks from TanStack Query to fetch the data and make the code more readable.
- UI gets instant reactive updates.
Project Overview
In this project, we will use Vite to create a fast development environment for the React project. For styling, we will use MUI. To efficiently fetch and cache the data from the API, we will use the TanStack Query, and for routing, we will use the TanStack Router.
We will use the Open demo API for developers to test their applications.
Step 1: Setup a new project using Vite
Open your terminal (like a command box) and type the command below:
npm create vite@latest tan-route-app -- --template react cd tan-route-app npm install
Step 2: Install TanStack Query, TanStack Router, MUI, Axios
Install the necessary dependencies mentioned in the title using the command below:
npm install axios @tanstack/react-query @tanstack/react-router @mui/material @emotion/react @emotion/styled
Step 3: Set up the theme for the project (theme.js)
We need to create a color theme file for our project.
Create a new folder called styles under the src folder. Let’s create a file called theme.js under the styles folder.
// src/styles/theme.js import { createTheme } from '@mui/material/styles'; const theme = createTheme({ palette: { primary: { main: '#1976d2', }, }, }); export default theme;
Step 4: Set up the query client
Create a new folder called “api” under the “src” folder, and inside the “api” folder, create a new file called “queryClient.js” and add the code below:
// src/api/queryClient.js import { QueryClient } from '@tanstack/react-query'; const queryClient = new QueryClient(); export default queryClient;
Step 5: Create API functions
We need to create API functions to fetch the data from an API. We will use the JSONPlaceholder API for this small project.
To send an HTTP GET request, we need to use the “axios.get()” function from the axios library.
Create a new file called “postApi.js” under the “api” folder and add the code below:
// src/api/postApi.js import axios from 'axios'; const fetchPosts = async () => { const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts'); return data; }; const fetchPost = async (id) => { const { data } = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`); return data; }; export { fetchPosts, fetchPost };
In this code, we defined two functions:
- fetchPosts: It returns the list of posts from the API.
- fetchPost: It returns a single post based on its id.
Step 6: Create custom hooks (usePosts.js)
We will create a custom hook that will use the two functions from the postApi.js file.
In this hook file, we will use the useQuery hook to fetch and cache the data from the server.
Create a new folder called hooks folder under the src folder.
Under the hooks folder, create a new file called “usePosts.js” and add the below code in it:
// src/hooks/usePosts.js import { useQuery } from '@tanstack/react-query'; import { fetchPosts, fetchPost } from '../api/postsApi'; export const usePosts = () => { return useQuery({ queryKey: ['posts'], queryFn: fetchPosts }); }; export const usePost = (id) => { return useQuery({ queryKey: ['post', id], queryFn: () => fetchPost(id), enabled: !!id, }); };
The useQuery() hook accepts an Object that has the following three arguments:
- queryKey: A unique key identifies this react query. You can use this key to fetch the data from the cache.
- queryFn: It is a function that fetches the data.
- enabled: Ensure the query only runs if the id is defined.
The usePosts() hook fetches and returns data for all posts using the fetchPosts() function.
The usePost() hook fetches and returns data for a specific post using an ID using the fetchPost() function.
Step 6: Create components
For this project, we will create four components under the components folder, which we will create in a second. These four components are the following:
- Home.jsx
- Navbar.jsx
- Posts.jsx
- PostDetails.jsx
Here is the code for Home.jsx:
// src/components/Home.jsx import { Typography, Container } from '@mui/material'; const Home = () => ( <Container> <Typography variant="h4" component="h1" gutterBottom> Welcome to My App </Typography> </Container> ); export default Home;
Here is the code for Navbar.jsx:
// src/components/Navbar.jsx import React from 'react'; import { AppBar, Toolbar, Typography, Button } from '@mui/material'; import { Link as RouterLink } from '@tanstack/react-router'; // Custom Link component that works with both MUI Button and TanStack Router const Link = React.forwardRef(({ to, ...props }, ref) => ( <RouterLink to={to} {...props} ref={ref} /> )); const Navbar = () => ( <AppBar> <Toolbar> <Typography variant="h6" sx={{ flexGrow: 1 }}> My App </Typography> <Button color="inherit" component={Link} to="/">Home</Button> <Button color="inherit" component={Link} to="/posts">Posts</Button> </Toolbar> </AppBar> ); export default Navbar;
This component is responsible for displaying navigation in our application.
Here is the code for Posts.jsx:
// src/components/Posts.jsx import { Link as RouterLink } from '@tanstack/react-router'; import { Container, Typography, CircularProgress, List, ListItem, ListItemText } from '@mui/material'; import { usePosts } from '../hooks/usePosts'; 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="h4" component="h1" gutterBottom> Posts </Typography> <List> {data.map(post => ( <ListItem key={post.id} button component={RouterLink} to={`/posts/${post.id}`}> <ListItemText primary={post.title} /> </ListItem> ))} </List> </Container> ); }; export default Posts;
This component shows a list of posts.
Here is the code for PostDetails.jsx:
// src/components/PostDetails.jsx import { useParams } from '@tanstack/react-router'; import { Container, Typography, CircularProgress } from '@mui/material'; 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;
This component shows the specific post.
Step 7: Setup a Router
Let’s set up the routing for our React application using TanStack Router.
Under the src folder, create a new file called router.js and add the code below:
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 createRootRoute() function creates a root route for the web application. The root route is the top-level route that wraps around all other routes.
The createRoute() function creates a second-level route for different parts of our application. This function accepts the following three arguments:
- getParentRoute: () => rootRoute: It specifies that the parent of this route is the rootRoute.
- path: It is a route URI path.
- component: It defines the specific component rendered when this route is accessed.
Then, we create a routeTree using rootRoute.addChildren() function and pass the array of components. In short, we organize the routes into a tree structure.
Then, we initialized the router using the createRouter() function and passed the routeTree to that function.
We define routes for the Home page, Posts page, and Post Detail page.
- For the home page, our URL will be “/”.
- For the posts page, our URL will be “/posts”.
- For the post detail page, our URL will be “/posts/1”.
Step 8: Setting up the App and Main components
Update the src/App.jsx component to use the Navbar component and render the child routes.
// src/App.jsx import { Outlet } from '@tanstack/react-router'; import Navbar from './components/Navbar'; import { Box } from '@mui/material'; const App = () => ( <div> <Navbar /> <Box sx={{ mt: 6, p: 6 }}> <Outlet /> </Box> </div> ); export default App;
Update the src/main.jsx file to configure everything properly to work with TanStack Query and TanStack Router.
// src/main.jsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { QueryClientProvider } from '@tanstack/react-query'; import { RouterProvider } from '@tanstack/react-router'; import { ThemeProvider } from '@mui/material/styles'; import theme from './styles/theme'; import queryClient from './api/queryClient'; import router from './router'; ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <QueryClientProvider client={queryClient}> <ThemeProvider theme={theme}> <RouterProvider router={router} /> </ThemeProvider> </QueryClientProvider> </React.StrictMode> );
Save this file, and here is the final directory structure for our application:
tan-route-app/ │ ├── public/ │ └── index.html │ ├── src/ │ ├── api/ │ │ └── postsApi.js │ │ │ ├── components/ │ │ ├── Navbar.jsx │ │ ├── Home.jsx │ │ ├── Posts.jsx │ │ └── PostDetails.jsx │ │ │ ├── hooks/ │ │ └── usePosts.js │ │ │ ├── styles/ │ │ └── theme.js │ │ │ ├── App.jsx │ ├── main.jsx │ └── router.js │ ├── package.json ├── package-lock.json ├── vite.config.js └── README.md
Step 9: Running the project
Start the Vite development server using the command below and check out the homepage: http://localhost:5173
npm run dev
If you click on the POSTS link, your URL becomes this: http://localhost:5173/posts/, and you will see the posts listing page like this:
If you click on the specific post, you will see the post detail page (http://localhost:5173/posts/1) like this:
Here is the complete code uploaded on Github.
That’s all!