Loading...
In this lesson we will learn about routing in our react application. We will learn about the react-router package. How to create routes and create a 404 page, We will create a navigation routes, learns how we can use the router to nest a parent component with child components. And we will learn how to transfer params using the url.
lets start with a really simple task, we will do the following:
- create a react app
- add the routing package
- create a single route that displays a homepage
React has a package that we can install that will start a new react application. That package is called: create-react-app.
Lets install it using NPM
> sudo npm install create-react-app -g
Now to start a new react application type in the terminal:
> create-react-app routing-tutorial
> cd routing-tutorial
> npm start
If all went well you should now be able to open your browser on localhost:3000 and view your app. Now lets install the react router package with npm:
> npm install react-router-dom --save
When using react-router on the browser you will have to install react-router-dom. Lets continue with routing hello demo, in the file App.js modify it to look like this:
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
function Homepage() {
return (
<h1>Homepage</h1>
);
}
class App extends Component {
render() {
return (
<BrowserRouter>
<Route path="/" component={Homepage} />
</BrowserRouter>
);
}
}
export default App;
When combining routing in our app we have to make sure that our entire application is wrapped in a BrowserRouter. So the first step in browsing is deciding which router to use which will usually be the BrowserRouter. The routing is now dynamically embedded in our app, so we can place wherever we want in our app route matching components. Route matching components will decide based on the route if a component needs to be displayed, for this small snippet we are familiar with the first one which is: Route which will match if the prefix of the current route match the property path. so basically we are saying if the route starts with / render the Home component, this pretty much match every route to the home component. If we want to render the home page only on the path / we need to set the exact property to true like we did and this will render only if the route match exactly /.
Lets create an about page to our app. We now want our app to contain these 3 routes:
- / - this will lead to the homepage
- /about - this will lead to the about page.
- every other route will lead to a 404 error page
We also will create a navigtation bar to point to the homepage and about page.
This time we will split the components to seperate files.
Lets start by creating 3 components: Home, About, Error404. Create a folder called components. In that folder create a file called Home.js with the following code:
import React from 'react';
export default function Home() {
return <h1>Homepage</h1>
}
just a simple component to display a homepage message. Now lets create another file in the components folder called About.js with the following code:
import React from 'react';
export default function About() {
return <h1>About page</h1>
}
We will do the same with the Error 404 component. In the components folder create a file called Error404.js With the following code:
import React from 'react';
export default function Error404() {
return <h1>Error path not found</h1>
}
Now lets connect those 3 components in our App.js Now we will need to use a different route matching component, only one of the components above could be displayed at any given time. The route matching component to achieve this is called Switch which will always return one match. Above the Switch we can place a common navigation area that will be shared among all the component routes. Change the App.js to look like this:
import React, { Component } from 'react';
import { BrowserRouter, Route, Switch, NavLink } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Error404 from './components/Error404';
class App extends Component {
render() {
return (
<BrowserRouter>
<React.Fragment>
<nav>
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/about">About</NavLink>
</li>
</ul>
</nav>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/about" component={About} />
<Route component={Error404} />
</Switch>
</React.Fragment>
</BrowserRouter>
);
}
}
export default App;
Few things to note here.
1. BrowserRouter can only have one child - that is why we need to wrap all the content inside a React.Fragment or a div element.
2. In the navigation bar we need to place links to the other pages. We cannot place links with an anchor tag cause this will cause the page to rerender. Instead we are importing another component NavLink which will place an anchor tag that will use the router and will not rerender the page.
Try launching the app you will see the navigation and the components we created.
We saw how the navigation bar can be shared among all the routes, but what if we have something shared but not in all the routes but in some of them. For example lets say we have an admin area in our app. The admin area has a section called user, and a section called dashboard. The admin area has a common parent that displays an hello admin message. In our components folder create a folder called admin. In the admin lets first start with the children component. Create a file called AdminUser.js with the following code:
import React from 'react';
export default function AdminUser() {
return <h1>Admin user section</h1>
}
Create another file called AdminDashboard.js with the following:
import React from 'react';
export default function AdminDashboard() {
return <h1>Admin dashboard section</h1>
}
Now lets create the common parent that is nesting those components. Create a file in the admin folder called Admin.js with the following code:
import React from 'react';
import { Switch, Route, NavLink, Redirect } from 'react-router-dom';
import AdminUser from './AdminUser';
import AdminDashboard from './AdminDashboard';
export default function Admin({match}) {
return (
<div>
<h1>Common part for admin routes</h1>
<nav>
<ul>
<li>
<NavLink to="/admin/user">User</NavLink>
</li>
<li>
<NavLink to="/admin/dashboard">Dashboard</NavLink>
</li>
</ul>
</nav>
<Switch>
<Route path={`${match.url}/user`} component={AdminUser} />
<Route path={`${match.url}/dashboard`} component={AdminDashboard} />
<Redirect to="/admin/user" />
</Switch>
</div>
);
}
The nesting of components is pretty stright forward and it's similar to how we made the nav only now the logic is in the parent component.
We can see that the router is passing through the component property a property called match.
the match object contains infromation about how the Route matched the url. It contains the following properties:
- params - we can grab url params from here, we will show example later on in the tutorial.
- isExact - true if the entire url was matched.
- path - is the string used to match with the name of the url params
- url - is the actual url that matched.
Another component we used here is the Redirect component that we can use if we want to direct the user to a url if we found no match.
We can add from property if we want a specific redirect from one route to another.
Now lets modify the App.js to contain the routing to the admin.
import React, { Component } from 'react';
import { BrowserRouter, Route, Switch, NavLink } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Error404 from './components/Error404';
import Admin from './components/admin/Admin';
class App extends Component {
render() {
return (
<BrowserRouter>
<React.Fragment>
<nav>
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/about">About</NavLink>
</li>
<li>
<NavLink to="/admin">Admin</NavLink>
</li>
</ul>
</nav>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/about" component={About} />
<Route path="/admin" component={Admin} />
<Route component={Error404} />
</Switch>
</React.Fragment>
</BrowserRouter>
);
}
}
export default App;
We often use the url to pass params to the next page.
We have two techniques for passing params in the url:
1 - via query params - usually used for search and filters.
2 - matrix params - params are passed as a section in the url: /questions/30/ - usually used for routes of resources that map to a database primary key or needed to crawl routes by search bots.
We will cover both of those techniques.
Lets say we have a todo application, and in that application we have a database with a table called tasks, the primary key of that table is a positive number.
In our app there is a route to display a todo task. That route is: /task/:pk/ where pk can be a changing number of the pk of the item we want to grab from the table.
Lets create a text input where you can enter a number and according to that number you will be directed to the proper task url and display the proper task.
Modify the App.js
import React, { Component } from 'react';
import { BrowserRouter, Route, Switch, NavLink } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Error404 from './components/Error404';
import Admin from './components/admin/Admin';
class App extends Component {
state = {
taskId: '',
}
directToTasks = (e) => {
this.history.push(`/task/${this.state.taskId}`);
e.preventDefault();
}
handleInputChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
});
}
render() {
return (
<BrowserRouter>
<React.Fragment>
<nav>
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/about">About</NavLink>
</li>
<li>
<NavLink to="/admin">Admin</NavLink>
</li>
</ul>
</nav>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/about" component={About} />
<Route path="/admin" component={Admin} />
<Route path="/search" component={({location}) => <h1>The query entered is: {location.search}</h1>} />
<Route path="/task/:id" component={({match}) => <h1>Now displaying task {match.params.id}</h1>} />
<Route component={Error404} />
</Switch>
<Route component={({history}) => {
this.history = history;
return null;
}} />
<form onSubmit={(e) => this.directToTasks(e)}>
<input type="number" value={this.state.taskId} name="taskId" onChange={this.handleInputChange} />
<button type="submit">Submit</button>
</form>
</React.Fragment>
</BrowserRouter>
);
}
}
export default App;
Few things to note in the code we changed here:
- We added the route /task/:id - notice that when the params are part of the url, we add :name-of-param syntax. We create a component function right in the component property of the route that grabs the id param through the match object.
- We added a form below the route - The form submit event will have to have access to the history of the router, the history is passed to components rendered by the router but our app component is not one of them, so we are rendering a weird move here in order to access the history from the app component (or unrendered by router component) we place a match all Route that will render a component that will return null, but that component will grab the history from the props and save it in the app component this.
We also made the text input a controled component so we can grab the text value from the state.
Now try to activate the app again type a number in the text field and notice that we are directed to a page that displays a title of the current task we are grabbing.
Lets try to do a similar thing with query params, we will add a search form, which will redirect to a search page that will display the query param passed in the form.
Again modify the App.js to this:
import React, { Component } from 'react';
import { BrowserRouter, Route, Switch, NavLink } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Error404 from './components/Error404';
import Admin from './components/admin/Admin';
class App extends Component {
state = {
taskId: '',
search: ''
}
directToTasks = (e) => {
this.history.push(`/task/${this.state.taskId}`);
e.preventDefault();
}
handleInputChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
});
}
directToSearch = (e) => {
this.history.push(`/search?q=${this.state.search}`);
e.preventDefault();
}
render() {
return (
<BrowserRouter>
<React.Fragment>
<nav>
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/about">About</NavLink>
</li>
<li>
<NavLink to="/admin">Admin</NavLink>
</li>
</ul>
</nav>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/about" component={About} />
<Route path="/admin" component={Admin} />
<Route path="/search" component={({location}) => <h1>The query entered is: {location.search}</h1>} />
<Route path="/task/:id" component={({match}) => <h1>Now displaying task {match.params.id}</h1>} />
<Route component={Error404} />
</Switch>
<Route component={({history}) => {
this.history = history;
return null;
}} />
<form onSubmit={(e) => this.directToTasks(e)}>
<input type="number" value={this.state.taskId} name="taskId" onChange={this.handleInputChange} />
<button type="submit">Submit</button>
</form>
<form onSubmit={(e) => this.directToSearch(e)}>
<input type="text" value={this.state.search} name="search" onChange={this.handleInputChange} />
<button type="submit">Submit</button>
</form>
</React.Fragment>
</BrowserRouter>
);
}
}
export default App;
Similar to before we directed with the history object of the router. We also added a route for search that prints the query. The location is passed in the props as well and we can grab the query string from there.
React router V4 has gone through amazing transformation, and defining the routes synamically like we are doing, once you get used to it is harder to go back to the static way of routing. Combining routing in your react app has never been easier.