In this post we'll see how to use data loader in React Router to provide data to route components.
Loader in React Router
Loader is a function that is used to fetch data before rendering a route component. That is different from the way useEffect() hook works which also provides a way to fetch data by making API calls. The useEffect() in react is called, after component has rendered, as a side effect.
With data loading in React Router, loader function is called for fetching data first then the route component is rendered. That ensures availability of the data when the component renders.
Data Loading React Router Example
In this example we'll use a loader function to fetch data from API. For API call https://jsonplaceholder.typicode.com/posts resource is used which is a free fake API. In the example Bootstrap 5 is used for styling.Components used
There is a navigation menu.
src\components\routes\NavigationNavLink.js
import { NavLink, Outlet } from "react-router"; import "./navigation.css"; const NavigationNavLink = () => { const style = (({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : "" ); return( <> <nav id="menu" className="navbar navbar-expand-lg bg-dark navbar-dark"> <div className="container-fluid"> <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarMenu" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span> </button> <div className="collapse navbar-collapse" id="navbarMenu"> <ul className="navbar-nav"> <li> <NavLink className={style} to="/"> Home </NavLink> </li> <li> <NavLink className={style} to="/post"> Post </NavLink> </li> </ul> </div> </div> </nav> <div className="container row mt-2"> <Outlet /> </div> </> ); } export default NavigationNavLink;
CSS used for styling the active link.
src\components\routes\navigation.css
#menu a:link, #menu a:visited { color: gray; } #menu a:hover { color: white; } #menu a.active { color: #7FFFD4; } #menu a { text-decoration: none; } #menu ul { gap: 1rem; }
Home component
src\components\routes\Home.js
const Home = () => { return ( <> <h2>This is home page</h2> </> ) } export default Home;
PostList component
This is the component which shows the fetched posts, loader function is also written in the same component.
src\components\routes\PostList.js
import { useLoaderData } from "react-router"; const PostList = () => { const postData = useLoaderData(); return( <div className="container"> <h2 className="text-info-emphasis text-center">Posts</h2> <ul className="list-group"> {postData.map((post) => <li className="list-group-item" key={post.id}> {post.id} {post.title} </li> )} </ul> </div> ) } export default PostList export async function loader(){ const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10'); // check for any error if(!response.ok){ // use Response object throw new Response("Failed to load data", { status: response.status }); }else{ const responseData = await response.json(); return responseData; } }
Some of the points to note here-
- In the component, loader function named loader is written.
- Uses fetch API to get data from the given URL. Only 10 posts are fetched.
- If there is no error in fetching data (response is ok) then you can return data directly with React Router V7. You don’t
need to wrap in json() function like this.
return json(await response.json());
- Other points are explained later in the post.
How to set loader in route
In the PostList component, loader function is defined but you have to let the router know that the specific loader function has to be called for the given route component. That is done using loader property in the router configuration.
src\components\routes\route.js
import { createBrowserRouter } from "react-router"; import Home from "./home"; import NavigationNavLink from "./NavigationNavLink"; import PostList, {loader as PostLoader} from "./PostList"; export const route = createBrowserRouter([ {path: "/", element: <NavigationNavLink />, children: [ {index: true, element: <Home /> }, {path: "post", element: <PostList />, loader: PostLoader}, ] }, ])As you can see loader function is imported as PostLoader
import PostList, {loader as PostLoader} from "./PostList";
and later provided in the route definition as a value of the loader property.
{path: "post", element: <PostList />, loader: PostLoader}
How to get loader data in components
Another question that arises is; how to access the data, fetched by the loader, in the component.
To access data in a component useLoaderData()
hook is used which returns data from the closest route loader
function.
That is how fetched post data is made available to the PostList component.
const postData = useLoaderData();
Which is then iterated to print id and title of each post.
Error handling in loaders
We should also take into consideration the scenario when loader is not able to fetch data because of some problem.
Couple of things we need to do here is to set the error message and status and also configure an error page to display the error.
When an error occurs during data fetching within a loader function, you can throw a Response object. With Response object you can include both status code and a message.
In the PostList component showed above Response object is already thrown.
if(!response.ok){ // use Response object throw new Response("Failed to load data", { status: response.status }); }
Error page component
You can create a separate component for displaying errors. To get the error message and status you can use
useRouteError()
hook.
src\components\routes\ErrorPage.js
import { isRouteErrorResponse, useRouteError } from "react-router"; const ErrorPage = () => { const error = useRouteError(); //console.log(error); if (isRouteErrorResponse(error)) { return ( <> <h1> {error.status} {error.statusText} </h1> <p>{error.data}</p> </> ); } else if (error instanceof Error) { return ( <div> <h1>Error</h1> <p>{error.message}</p> <p>The stack trace is:</p> <pre>{error.stack}</pre> </div> ); } else { return <h1>Unknown Error</h1>; } } export default ErrorPage;
You also need to set the Error page using the errorElement prop in the parent route. Even if an error occurs in the Child component, it will bubble up to the Root route where <ErrorPage /> component defined in the errorElement prop will catch that error.
{path: "/", element: <NavigationNavLink />, errorElement: <ErrorPage />,
For more details about setting error page, refer this post- Setting 404 Error Page With React Router
Loading state
While the data is fetched by the loader, if you want to show a loading message for better user experience then you can use
useNavigation()
hook to do that.
useNavigation hook provides access to the state of navigation, which are as following-
- idle: No navigation is happening.
- loading: A navigation is in progress.
- submitting: A form submission is in progress.
With ReactRouter V7 you should also provide hydratefallbackelement
.
Using useNavigation hook to show loading state
If you are using useNavigation hook then it should be used one level above the current route. Which is logical because component is rendered only after fetching data when you are using data loading in routes. In our example navigation menu component is the suitable choice for showing loading state.
Changes needed in the NavigationNavLink component are to import useNavigation() hook and get the current navigation.
import { useNavigation } from "react-router"; then const navigation = useNavigation();
In current navigation you can check the state if it is “loading” then display loading state. For simplicity here a simple message is displayed.
For that instead of <Outlet />, use this
{navigation.state === "loading" ? <h2>Loading!</h2>:<Outlet />}
Then loading message is displayed while data is fetched.
Using hydratefallbackelement property
You should also add the hydrateFallbackElement
to your router configuration, otherwise you may get warning
for the same.
{path: "post", element: <PostList />, loader: PostLoader, hydrateFallbackElement: <h2>Loading...</h2>,},
With fetched data
That's all for this topic Data Loader in React Router. If you have any doubt or any suggestions to make please drop a comment. Thanks!
Related Topics
You may also like-
No comments:
Post a Comment