This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.
Ext JS introduced routes as an enhancement over the history class used to coordinate browser history and an application UI. What routes offers is the ability to intimately map the views in the UI to the browser’s address bar. React applications may also take advantage of routes to render the correct UI based on the current URL. In this article we’ll look at the leading router solution in the React ecosystem, react-router. We’ll explore how you can define your application to predictably render all aspects of your application using the browser URL, including user navigation.
Note: Full disclosure, Modus Create is a sponsor of react-router so, yeah, we like it.
Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.
React Router Overview
Install react-router:
npm install --save react-router-dom
The react-router module handles interacting with the browser’s address bar and history manager. In Ext JS the router handled only the portion of the url following the hash (or optionally #! in later versions). Routes with react-router may be handled using either the content within the hash or within the URL itself. You can set up routing using the HashRouter component to handle routes like:
appRoot#view/subview/itemId
However, react-router recommends using the BrowserRouter component instead to handle routes defined in the url itself ahead of any hash:
appRoot/view/subview/itemId
The BrowserRouter
component uses the HTML5 history API and will not be compatible with all browsers. Additionally, you’ll need to ensure that your web server is configured to allow real url navigation versus folder navigation.
react-router Simple Route
Let’s set up a simple route example to demonstrate how react-router works. In this first example, we’ll set up our App with two views that will be rendered depending on the URL. Let’s create the following files:
src/Home.js
import React from 'react'; export default () => <h2>Home</h2>;
src/User.js
import React from 'react'; export default () => <h2>User</h2>;
src/App.js
import React from 'react'; import { BrowserRouter as Router, Route } from 'react-router-dom'; import Home from './Home'; import User from './User'; const App = () => ( <Router> <div> <Route exact path="/" component={Home} /> <Route path="/user" component={User} /> </div> </Router> ); export default App;
Our App
class serves as a container for either Home
or User
depending on the URL used. If we run npm start
in the terminal, we’ll see “Home” in the browser since by default the URL loaded is the root URL of http://localhost:3000/. The Home component is shown by navigating to the application root by stipulating the Route as:
<Route exact path="/" component={Home}/>
The path value of “/
” tells react-router that when we’re at the root with no additional pathing in the URL the Home
component is to be rendered. If we want to render the User view instead, we can navigate to http://localhost:3000/user. Since our user route is defined with a path of “/user” the User
component, not the Home
component, will be rendered.
You can review the sample code on the Git repo.
react-router Nested Routes and Params
Now that we have a simple routing example laid out, let’s add nested routes to it. Most applications won’t be a single layer deep. Our User class, for example, could be a container displaying a list of users by default. Additionally, if a user’s id is appended to the URL, then that user’s info would display in an edit form. Think of clicking a row in a master grid to view record details. Let’s modify the User
class and add a UserList
and UserForm
class as child components of User.
src/User.js
import React from 'react'; import { BrowserRouter as Router, Route } from 'react-router-dom'; import UserForm from './UserForm'; import UserList from './UserList'; export default ({ match }) => ( <div> <UserList /> <Router> <Route path={`${match.url}/:userId`} component={UserForm}/> </Router> </div> );
src/UserList.js
import React from 'react'; export default () => <h2>User List</h2>;
src/UserForm.js
import React from 'react'; export default (props) => { const { userId } = props.match.params; return <h2>User Id: {userId}</h2>; };
Our User
class now displays the UserList
as a child and optionally a UserForm
component if the URL has a value after “user/” in the URL. For this example, we could assume that that would be a user’s ID and the URL might look like:
http://localhost:3000/user/1234
If the URL were only http://localhost:3000/user
then the User
view and the UserList
would be rendered. The path
attribute of our User
class’s Route
is {`${match.url}/:userId`}
. The use of “:
” in the path means that any value supplied in the URL will match and be will automatically passed down to the component specified on the Route. Its value will be stored as a param on the match
prop passed to the Route’s component, UserForm
in the case of our example. Here we render the value in the UserForm
in plain text.
const { userId } = props.match.params; … <h2>User Id: {userId}</h2>
However, this user ID value could easily be used to asynchronously fetch user data to populate the UserForm
once mounted.
You can review the sample code on the Git repo.
react-router Route Navigation
Now that we’ve looked at creating the route structure, let’s look at how to populate the UI to activate a route either programmatically or via user interaction.
Programmatic Navigation
First, let’s look at how the route can be activated in the logic of a class. The react-router module wraps its internal history api and can be used to set the URL or url segment programmatically. As an arbitrary example to demonstrate how routing can be navigated let’s make an arbitrary change to our example UserForm class:
const UserForm = (props) => { const { userId } = props.match.params; const { history } = props; if (userId === '1234') { history.push('abcd'); } return ( <div> <h2>User Id: {userId}</h2> </div> ) }
A component rendered via a Route
will have history
passed as a prop from the Route. We can use history’s push
method to swap the current matched portion of the route with another string. The conditional in the example above does just that. Setting the URL to http://localhost:3000/user/1234
results in the URL changing to http://localhost:3000/user/abcd
as the UserForm
is rendered. The push
method adds the URL to browser history, which in this case may not be the desired action. The replace
method will instead swap the current URL in the browser’s history with the one passed in.
You can review the sample code on the Git repo.
Simple Navigation
Link
components can be used for user navigation as you would use an anchor tag. Let’s modify our example app to have three navigation links at the top of the view. We’ll also add an About
class as a peer of User
and Home
. The links allow a user to select the home, user, or about view to display below the links.
src/About.js
const About = () => ( <div> <h2>About</h2> </div> )
src/App.js
import React from 'react'; import { BrowserRouter as Router, Link, Route } from 'react-router-dom'; import About from './About'; import Home from './Home'; import User from './User'; const App = () => ( <div> <Router> <div> <Link to="/">Home<br/></Link> <Link to="/user">User<br/></Link> <Link to="/about">About</Link> <Route exact path="/" component={Home}/> <Route path="/user" component={User}/> <Route path="/about" component={About}/> </div> </Router> </div> ); export default App;
Here we’ve added three Link
components to the parent Router
with to
attributes that mirror the paths defined on each Route
. Clicking on the text from each Link will update the URL which, in turn, updates the view.
You can review the sample code on the Git repo.
Tab Navigation
Lastly, let’s modify our previous Link
example to instead use the NavLink
component. The NavLink is an enhanced Link component that adds a className
and /
or style
prop to the NavLink instance when the current URL matches the NavLink’s to
prop. The NavLink component makes it easy style the navigation elements based on the current URL resulting in UI’s like we’re used to with Ext JS’s Tab Panel.
src/App.js
import React from 'react'; import { BrowserRouter as Router, NavLink, Route } from 'react-router-dom'; import About from './About'; import Home from './Home'; import User from './User'; import './App.css'; const App = () => ( <div> <Router> <div className="nav-links"> <NavLink exact activeClassName="active" to="/">Home</NavLink> <NavLink exact activeClassName="active" to="/user">User</NavLink> <NavLink exact activeClassName="active" to="/about">About</NavLink> <Route exact path="/" component={Home}/> <Route path="/user" component={User}/> <Route path="/about" component={About}/> </div> </Router> </div> ); export default App;
In this example, we add a className
of “nav-links” to the div containing the NavLinks
so that we can decorate the navigation elements like tabs. The activeClassName
prop on each NavLink
serves to add “active” as a class name when the Route path
and NavLink to
props match which shows an “active tab” decoration. For completeness, here is the CSS you might use for tab styling:
src/App.css
.nav-links a { display: inline-block; border-bottom: 4px solid transparent; padding: 6px 12px; text-decoration: none; color: #555; } .nav-links a.active { border-bottom-color: #6597ca; }
You can review the sample code on the Git repo.
Conclusion
Routing enables your application to operate in the browser’s address bar similar to how you might expect a website to behave. Each view within your site relates to the browser URL which allows your users to not link to not only the application front page, but also any route-enabled view within the application. In addition to user convenience, routing offers a centralized navigation pattern for your application to follow where navigable views are rendered by the router’s built-in conditional logic. As we’ve said, we’re big fans of react-router. However, it’s not the only router package available. Have you found a router module you’ve fallen in love with? Please share your experiences in the comments below!
Seth Lemmons
Related Posts
-
Ext JS to React: Migration to Open Source
Worried about Migrating from Ext JS? Modus has the Answers Idera’s acquisition of Sencha has…
-
Ext JS to React: FAQ
This is part of the Ext JS to React blog series. React is Facebook's breakout…