Around September 2022, the Remix team introduced React Router 6.4, packed with latest features and patterns that streamline various tasks within your React application.
These enhancements are designed to simplify behaviors like loading and updating data, ensuring automatic data revalidation after updates, handling navigation race conditions, managing errors effectively, creating pending and skeleton UIs, and more, all based on the Remix team's insights.
In this article, we'll mainly delve into the following aspects:
- Page Navigation
- Data Fetching
- Loading Data into Components
- Efficient Error Management and User-Centric Rendering
While the allure of trying out new features can be strong, it's advisable to balance innovation with what already works for you. Just like preferring new clothes over old ones. You should adopt features that suit your project's needs.
Before embracing these enhancements in your next project, take the time to comprehend how they function. This article serves as your guide in understanding and implementing these exciting features effectively. Let’s get started. 😊
Prerequisites
Before we continue, make sure you have your project setup in your local machine.
- Basic understanding of React.
- Install the new react application and run the application
- Install the latest version of
react-router-dom
→npm install react-router-dom
Page navigation
With React Router 6.4, navigating through your application's pages has become remarkably simpler, requiring fewer lines of code.
Page navigation, or routing, is an indispensable aspect of building React applications. It's a fundamental skill that enables you to showcase content dynamically based on specific routes, all without the need for page refresh. This not only leads to a smoother user experience but also optimizes your application's performance.
There are instances where you want to seamlessly display content as users explore different sections of your app, and React Router 6.4 equips you with the tools to achieve this efficiently.
i. Setting up Routes in React Router 6.4+
Prior to delving into route configuration, ensure that react-router-dom
is present in your package.json
file. If it's not already there, you can add it by executing the command npm install react-router-dom
in your terminal. This step is essential as it provides the foundation for establishing and managing routes using React Router 6.4+. Once you have this dependency in place, you'll be ready to define and organize the navigation paths within your application effectively.
Now, let's delve into router configuration.
To get started, we import createBrowserRouter
from react-router-dom
. This function plays a crucial role in directing the routing of our application within the updated router version. This crucial step sets the stage for defining how different routes will guide users through your application's various pages and components. With createBrowserRouter
, you gain the power to establish clear paths for seamless navigation, making the most out of React Router 6.4's capabilities.
import { createBrowserRouter, createRoutesFromElements, RouterProvider, Route, Link, Outlet } from 'react-router-dom'
Inside your App.js
component, declare a variable named router and assign it to createBrowserRouter
function.
Configuring routes involves two distinct approaches, each offering its advantages:
- Array of Objects: One option is to define routes through an array of objects. Each object contains properties such as path and element. This clear configuration helps map out the paths and associated components for navigation.
const router = createBrowserRouter([
{ path: '/', element: <Home />},
{ path: '/about', element: <About /> }
])
-
Using
createRoutesFromElements
: Alternatively, leverage createRoutesFromElements within createBrowserRouter. Here, you use route definitions as JSX elements. The primary route, functioning as a parent, shapes the layout that accommodates all subsequent child routes.
const router = createBrowserRouter(createRoutesFromElements(
<Route path='/' element={<Root />} >
<Route index element={<Home />} /> //default route
<Route path='/about' element={<About />} />
</Route>
));
As you proceed, it's important to note that the Root component, if use as an element within a Route, might require creating it in a separate file or even within the same App.js
file, depending on the project's structure and your preferences. This ensures a well-organized and efficient routing structure.
ii. Navigating between pages in a client-side application using React Router 6.4+
In the context of this article, we'll illustrate page navigation using two(2) components:
Home component and About component
//Content of the Home Component
const Home = () => {
return (
<div>This is the Home page</div>
)
}
export default Home
//Content of the About Component
const About = () => {
return (
<div>This is the About page</div>
)
}
export default About
Incorporate the links to our pages using the <Link>
from react-router-dom, ensuring alignment with the corresponding paths defined in the router variable.
When utilizing the <Link>
, the to
attribute plays a crucial role. Essentially, clicking on the text "About" triggers the inclusion of /about
in the page's URL. Subsequently, the designated component with the identical path from the router variable, in this instance <Route path='/about' element={<About />} />
, gets rendered.
This seamless interaction enables users to effortlessly navigate between content while maintaining a coherent and engaging user experience.
//Root component or element
const Root = () => {
return (
<div>
<header>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</header>
<main>
<Outlet/>
</main>
</div>
)
}
A closer observation reveals that the parent Route within the router variable, housing the Root component, holds the path ‘/’
. Consequently, when launching the application, the default route, which is Home, is displayed due to its position as the index route within the Root Route.
Within the primary element, the <Outlet/>
takes center stage. Its purpose is to seamlessly render the content of the Route nested within the Root Route.
However, at this juncture, our root file - App.js
, lacks direct access to the previously crafted router variable. To bridge this gap, the solution lies in utilizing the <RouterProvider/>
from react-router-dom. By embedding router as a prop within <RouterProvider/>
.
<div className='App'>
<RouterProvider router={router} />
</div>
Here is our final result working as intended.
Data Fetching
In React Router 6.4+, data fetching assumes a crucial role, facilitating swift data retrieval each time a page is rendered. If you're keen to delve deeper, I've penned an article detailing data access within components, leveraging React Hooks.
Feel free to explore it: How to pass and access data from one route to another with useLocation, useNavigate, useHistory hooks.
For software developers, data fetching ranks high in importance when building applications. In essence, data fetching entails retrieving information from a server and seamlessly presenting it in response to user requests.
i. How to load data from API in React Router 6.4+
The latest React Router version has made fetching data from APIs incredibly simple, reducing the amount of code required.
This is achieved through the use of loader
props, which streamline the process of fetching data from an API before the component is displayed in the browser. As a result, there's no longer a need to employ the useEffect
hook to fetch data during component rendering. It's like achieving two goals with a single action.
Traditionally, when fetching data in a component, developers used the useEffect hook to fetch data and often utilized the useState hook to store it. In React Router 6.4+, this process is simplified further using the useLoaderData()
hook. This means you don't have to rely on either the useEffect or useState hook to store API data.
In our illustration, we'll work with the free Pokemon API and create a dedicated page to showcase this streamlined data loading approach.
const Poke = () => {
const result = useLoaderData()
return (
<div>
This is the Poke page
{
data.game_indices.map(itm => (
<div key={itm.version.name}>
<p >
{itm.version.name}
</p>
</div>
))
}
</div>
)
}
export default Poke
export async function pokeData(){
const response = await fetch('https://pokeapi.co/api/v2/pokemon/ditto')
.then(response => response.json())
const resData = await response
return resData;
}
Examining the code above, we're using the fetch API to grab data from the Pokemon API. Importantly, getting data doesn't have to happen in the exact same place where we want to show it.
We can actually fetch data in a different component, then bring the function that fetches the data over and connect it to a specific pathway.
When we want to display the data we fetched using the pokeData
function in the Poke component, we'll need to tap into something called "loader props". This is like a special tool that the Poke component gets. With this tool, we hand over the pokeData
function, and it makes sure the data is fetched and ready to be shown when the Poke component shows up. It's a bit like preparing all the ingredients before cooking a meal. This way, things are nicely organized and work smoothly.
import './App.css'
import { createBrowserRouter, createRoutesFromElements, RouterProvider, Route, Link, Outlet } from 'react-router-dom'
import Home from './page/Home'
import About from './page/About';
import Poke from './page/Poke';
import { pokeData } from './page/Poke' //imported data function
function App() {
const router = createBrowserRouter(createRoutesFromElements(
<Route path='/' element={<Root />} >
<Route index element={<Home />} />
<Route path='/about' element={<About />} />
<Route
path='/poke'
element={<Poke />}
loader={pokeData}
/>
</Route>
));
return (
<div className='App'>
<RouterProvider router={router} />
</div>
)
}
So whenever we visit /poke
route, the pokeData function will run ahead of time, fetch the data, store the data response in the result by setting it to useLoaderData()
hook from react-router-dom, and voila………..! you have access to render the data in that page.
Error handling
Sometimes, when you go to a page, you might see a big "404 Not Found" message with some other explanations. This basically means that the page you're looking for isn't there or doesn't exist.
You wouldn't like to present users with a strange-looking page like that when they're trying to reach the poke page, especially if the API data isn't working or no response is returned.
The good news is, React Router 6.4+ makes it simple for you to manage these situations. It provides ways to show a more attractive error page using tools like errorElement
props and useRouteError()
hooks.
i. How to handle and render errors with useRouteError hook
To begin, we'll adjust our fetch API function to enable us to see if the fetch was successful or not. You can find guidance on this at MDN: Checking that fetch was successful.
export async function pokeData() {
try {
const res = await fetch(
"https://pokeapi.co/api/v2/pokemon/ditto"
);
const resData = await res.json();
if (!res.ok) {
throw Error('Name is not available!');
}
return resData;
} catch (error) {
throw Error('Name is not available!');
}
}
Create a separate page component for the Error Page, import useRouteError
hooks from react-router-dom
, and assign it to a variable which will be the error that you throw in pokeData function. Using the variable you can be able to access the error message on that page.
Create a new component for the Error Page, and bring in the useRouteError
hooks from react-router-dom
. Set it to a variable, which will hold the error you create in the pokeData function. This variable lets you get the error message and display it on that page.
import { Link, useRouteError } from "react-router-dom"
const PokeError = () => {
const error = useRouteError();
return (
<div>
<p>{error.message}</p>
<Link to='/'>Back to home page</Link>
</div>
)
}
export default PokeError
Pass the error using the errorElement
props into the associated Route and simply give it the Error page as the value.
import './App.css'
import { createBrowserRouter, createRoutesFromElements, RouterProvider, Route, Link, Outlet } from 'react-router-dom'
import Home from './page/Home'
import About from './page/About';
import Poke from './page/Poke';
import { pokeData } from './page/Poke'
import PokeError from './page/PokeError';
function App() {
const router = createBrowserRouter(createRoutesFromElements(
<Route path='/' element={<Root />}>
<Route index element={<Home />} />
<Route path='/about' element={<About />} />
<Route
path='/poke'
element={<Poke />}
loader={pokeData}
errorElement = { <PokeError/>}
/>
</Route>
));
return (
<div className='App'>
<RouterProvider router={router} />
</div>
)
}
Note: You have two options: you can use the errorElement
props in either the main parent Route or the associated Route.
If the errorElement props are set in the parent Route, and you attempt to go to an invalid route like /pokes
, the Error page will show up as expected. However, the error message specific to the Poke page won't display, and you'll see the other content instead.
In conclusion, React Router 6.4+ brings exciting features that make building React applications easier. It simplifies tasks like loading and updating data, managing errors, and improving the user interface.
Navigating between pages is now simpler and faster with the new routing setup. Data fetching, which is crucial, is made easier with the useLoaderData() hook. It loads data before the page is rendered, resulting in quicker loading times. Handling errors is also improved, making error messages more user-friendly.
React Router 6.4+ provides valuable tools that enhance navigation, data management, and error handling, making React development more efficient and user-focused.
Top comments (0)