It was exciting to hear about the React Router announcement of its upcoming major version React Router version 6, which is still under development. The alpha version has been released for the developer community to start testing and checking out its new features. I am trying out the same, and sharing my learnings in this blog with you.
This is a major version which introduces many powerful new features. It also brings about better compatibility with the latest versions of React(React v16.8 or greater)
React Router: Main features
- Relative routes + links
- Nested routes
- Automatic route ranking
- Suspense-ready navigation
- Object-based route config with useRoutes
Let’s take a closer look at each of these features and understand what this means for the React Router.
We will create a test app to try out all these features.
Create your new app as below
npx create-react-app new-routing-app
Note: In case you have been using React for a while now and have not yet tried the new version of create-react-app, you may have to uninstall any old versions of globally installed create-react-app using the below command before running the above npx command:
npm uninstall -g create-react-app
If the above doesn’t work and you get an incomplete app folder created(this happened with me), try the below command which ensures the latest version of create-react-app gets used and your project gets created properly:
npx create-react-app@latest new-routing-app
Install axios as an additional dependency
npm install –save axios
Now let’s explore the react router features of version 6, by using them to build our simple routing application.
1. Relative routes + links
Let’s now define the routes for our test application.
We will define an application for Users with 2 main routes and 2 child routes as listed:
- Home (‘/’)
- View all Users(‘/users’)
- View specific User(‘/users/:id’)
- Just Another Link(‘/new’)
With the new Routes tag, we have the following benefits:
- All <Route>s and <Link>s inside <Routes> are relative.
- Routes are chosen based on the best match instead of being checked in the order of appearance. That’s the reason you don’t need switch, strict and exact!
So my first level routes using the new tags can be defined as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import React from 'react'; import './App.css'; function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="users/*" element={<Users />} /> </Routes> </BrowserRouter> ); } export default App; |
You would have noticed the use of element instead of component on the Route tag. By being able to provide an element to the route, it becomes easier to use the jsx element and be able to pass props to the component in the same Route statement as below where users are being sent as props to the UserDetails component:
1 |
<Route path=":id" element={UserDetails users={users}/} /> |
Using the above approach, any data can be passed as props to the route element and accessed as normal props.
Also, the route params can be obtained by using the useParams hook as below:
1 2 |
let params = useParams(); console.log(params.id); |
Another useful hook to be used to navigate to another path within code is useNavigate which we have used to navigate as below:
1 2 3 4 |
let navigate = useNavigate(); const selectUser = (id )=>{ navigate('/users/'+id); }; |
2. Nested routes
Defining child routes or nested routes is also much more straightforward with this version.
In our example, we can now define the child routes within the Users component without having to worry about the relative path which is done in older versions by extracting the current path and matching it. Instead, it will automatically build and detect the parent’s path. So we can just define the path at the child level component as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import React from 'react'; import './Home.css'; const Users = (props) => ( <div> <nav> <Link to="new">Add New User</Link> </nav> <Routes> <Route path=":id" element={UserDetails users={users}/} /> <Route path="new" element={<AddNew />} /> </Routes> </div> ); export default Users; |
Another approach of defining the above child routes is at one place at the top level App.js as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="users" element={<Users />}> <Route path=":id" element={UserDetails /} /> <Route path="new" element={<AddNew />} /> </Route> </Routes> </BrowserRouter> ); } function Users() { return ( <div> <nav> <Link to="me">My Profile</Link> </nav> <Outlet /> </div> ) } |
This approach can be good for simple applications to have all routes defined in one place along with the hierarchy using nested Route tags as above. Note that now we do not have the * for the parent route as we are providing the possible child routes at the same place. You will need the * if there are still some more child routes not included in the top list.
Also, note that we will need to include an Outlet in the child component which will render the child routes as shown in the Users component above.
3. Automatic route ranking
The syntax for providing patterns for the route path is simplified to facilitate automatic route ranking. It supports only 2 kinds of placeholders: dynamic:id-style params and * wildcards, which can be used only at the end of a path. It also ignores the trailing slash on the URL.
Some valid path examples include
users
users/:id
users/*
users/new
users/:id/*
users/new/*
You cannot use RegExp or wild cards in the middle of the path now. This ensures automatic route ranking and matching with the patterns avoiding any need for Switch, exact, strict, etc.
4. Suspense-ready navigation
The React-router version 6 supports lazy loaded paths, where the lazy loaded components can be provided to the element property directly. The routes can be defined as lazy loaded by importing it using React.lazy as below:
1 2 |
let AddNew = React.lazy(() => import("./AddNew"));//lazy loaded path let UserDetails = React.lazy(() => import("./UserDetails"));//lazy loaded path |
They will be handled with Suspense and given directly as the element to be loaded for the path as below:
1 2 3 4 5 6 |
<Suspense fallback="<div>Loading...</div>"> <Routes> <Route path=":id" element={<UserDetails users={users}/>} /> <Route path="new" element={<AddNew />} /> </Routes> </Suspense> |
This will avoid these routes from being loaded during the initial load and they will only get loaded when the user navigates to them. This also solves an old issue where the lazy-loaded components could not be provided as the value for component on the Route path definition directly and required some workaround using render or propTypes.
5. Object-based route config with useRoutes
If you like to define your routes as JavaScript objects instead of using React elements as we earlier did, version 6 has brought along useRoutes hook.
So you can also define your routes as below in the form of object configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function App() { let element = useRoutes([ // These are the same as the props you provide to <Route> { path: '/', element: <Home /> }, { path: 'users', element: <Users />, // Nested routes use a children property, which is also // the same as <Route> children: [ { path: ':id', element: <UserDetails /> }, { path: 'new', element: <AddNew /> } ] }, // Redirects use a redirectTo property to { path: 'home', redirectTo: '/' }, // Not found routes work as you'd expect { path: '*', element: <AddNew /> } ]); // The returned element will render the entire element // hierarchy with all the appropriate context it needs return element; } |
Things to remember when migrating to React-router version 6:
Below is a summarized list of the main changes in version 6, which need to be taken care of when migrating from an older version:
- Replace Switch tags with Routes.
- Remove use of exact, strict in paths.
- Update Route path to follow the given patterns(: id and * at end of route pattern).
- Define relative and nested routes without the use of the current match.url.
- Replace react-router-config with useRoutes hooks to define route configurations in object format.
- Replace useHistory with useNavigate to perform navigation in code logic.
- Replace component/render property with element in Route path definitions.
- Use Suspense and React.lazy to load routes lazily by providing property element as the lazily loaded component.
- Use the useParams hook to get the parameter values from the current route.
You can refer to the React official documentation on the migration guide here.
You can also refer to the sample application which we created in this blog using React-router version 6, the code can be found here.
Conclusion
React-router is one of the most important libraries in the React ecosystem. With the new upcoming version, it becomes even easier to use this package to achieve routing in your React application as it removes some of the unnecessary complexities. There will surely be some breaking changes if you are already using an older version but it may be a major release worth upgrading to, once it is officially out.
Till then let’s wait and be prepared!