01、Setting up the Project
First you'll need Node.js and the package manager
that comes with it: npm.
Once you've got that working, head to the command line where we'll set
up our project.
Clone the Tutorial
git clone https://github.com/reactjs/react-router-tutorial
cd react-router-tutorial
cd lessons/01-setting-up
npm install
npm start
Now open up http://localhost:8080
Feel free to poke around the code to see how we're using webpack and npm
scripts to run the app.
You should see a "Hello React Router" message in the browser.
Make Some Changes
Open up modules/App.js and change the text to something like "Hello
<your name>". The browser automatically reloads with your new code.
02、 Rendering a Route
At its heart, React Router is a component.
render(<Router/>, document.getElementById('app'))
That's not going to display anything until we configure a route.
Open up index.js and
- import
Router,Route, andhashHistory - render a
Routerinstead ofApp
// ...
import { Router, Route, hashHistory } from 'react-router'
render((
<Router history={hashHistory}>
<Route path="/" component={App}/>
</Router>
), document.getElementById('app'))
Make sure your server is running with npm start and then visit
http://localhost:8080
You should get the same screen as before, but this time with some junk
in the URL. We're using hashHistory--it manages the routing history
with the hash portion of the url. It's got that extra junk to shim some
behavior the browser has natively when using real urls. We'll change
this to use real urls later and lose the junk, but for now, this works
great because it doesn't require any server-side configuration.
Adding More Screens
Create two new components at:
modules/About.jsmodules/Repos.js
// modules/About.js
import React from 'react'
export default React.createClass({
render() {
return <div>About</div>
}
})
// modules/Repos.js
import React from 'react'
export default React.createClass({
render() {
return <div>Repos</div>
}
})
Now we can couple them to the app at their respective paths.
// insert into index.js
import About from './modules/About'
import Repos from './modules/Repos'
render((
<Router >
<Route path="/" component={App}/>
{/* add the routes here */}
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Router>
), document.getElementById('app'))
Now visit http://localhost:8080/#/about and
http://localhost:8080/#/repos
03、 Navigating with Link
Perhaps the most used component in your app is Link. It's almost
identical to the <a/> tag you're used to except that it's aware of
the Router it was rendered in.
Let's create some navigation in our App component.
// modules/App.js
import React from 'react'
import { Link } from 'react-router'
export default React.createClass({
render() {
return (
<div>
<h1>React Router Tutorial</h1>
<ul role="nav">
<li><Link to="/about">About</Link></li>
<li><Link to="/repos">Repos</Link></li>
</ul>
</div>
)
}
})
Now visit http://localhost:8080 and click the links, click back, click
forward. It works!
04、 Nested Routes
The navigation we added to App should probably be present on every
screen. Without React Router, we could wrap that ul into a
component, say Nav, and render a Nav on every one of our screens.
This approach isn't as clean as the application grows. React Router
provides another way to share UI like this with nested routes, a trick
it learned from Ember (/me tips hat).
Nested UI and Nested URLs
Have you ever noticed your app is just a series of boxes inside boxes
inside boxes? Have you also noticed your URLs tend to be coupled to that
nesting? For example given this url, /repos/123, our
components would probably look like this:
<App> {/* / */}
<Repos> {/* /repos */}
<Repo/> {/* /repos/123 */}
</Repos>
</App>
And our UI something like:
+-------------------------------------+
| Home Repos About | <- App
+------+------------------------------+
| | |
Repos -> | repo | Repo 1 |
| | |
| repo | Boxes inside boxes |
| | inside boxes ... | <- Repo
| repo | |
| | |
| repo | |
| | |
+------+------------------------------+
React Router embraces this by letting you nest your routes, which
automatically becomes nested UI.
Sharing Our Navigation
Let's nest our About and Repos components inside of App so that we
can share the navigation with all screens in the app. We do it in two
steps:
First, let the App Route have children, and move the other routes
underneath it.
// index.js
// ...
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
{/* make them children of `App` */}
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Route>
</Router>
), document.getElementById('app'))
Next, render children inside of App.
// modules/App.js
// ...
render() {
return (
<div>
<h1>React Router Tutorial</h1>
<ul role="nav">
<li><Link to="/about">About</Link></li>
<li><Link to="/repos">Repos</Link></li>
</ul>
{/* add this */}
{this.props.children}
</div>
)
}
// ...
Alright, now go click the links and notice that the App component
continues to render while the child route's component gets swapped
around as this.props.children :)
React Router is constructing your UI like this:
// at /about
<App>
<About/>
</App>
// at /repos
<App>
<Repos/>
</App>
By Small and Simple Things are Great Things Brought to Pass
The best way to build large things is to stitch small things together.
This is the real power of React Router, every route can be developed
(even rendered!) as an independent application. Your route configuration
stitches all these apps together however you'd like. Applications
inside of Applications, boxes inside of boxes.
What happens if you move the About route outside of App?
Okay, now put it back.
05、 Active Links
One way that Link is different from a is that it knows if the path
it links to is active so you can style it differently.
Active Styles
Let's see how it looks with inline styles, add activeStyle to your
Links.
// modules/App.js
<li><Link to="/about" activeStyle={{ color: 'red' }}>About</Link></li>
<li><Link to="/repos" activeStyle={{ color: 'red' }}>Repos</Link></li>
Now as you navigate, the active link is red.
Active Class Name
You can also use an active class name instead of inline-styles.
// modules/App.js
<li><Link to="/about" activeClassName="active">About</Link></li>
<li><Link to="/repos" activeClassName="active">Repos</Link></li>
We don't have a stylesheet on the page yet though. Lets add one-extra
point if you can add a link tag from memory.
// index.html
<link rel="stylesheet" href="index.css" />
And the CSS file:
.active {
color: green;
}
You'll need to manually refresh the browser since Webpack isn't building
our index.html.
Nav Link Wrappers
Most links in your site don't need to know they are active, usually just
primary navigation links need to know. It's useful to wrap those so you
don't have to remember what your activeClassName or activeStyle is
everywhere.
We will use a spread operator here, the three dots. It clones our props
and in this use case it clones activeClassName to our desired component for
us to benefit from.
Create a new file at modules/NavLink.js that looks like this:
// modules/NavLink.js
import React from 'react'
import { Link } from 'react-router'
export default React.createClass({
render() {
return <Link {...this.props} activeClassName="active"/>
}
})
Now you can go change your links to NavLinks.
// modules/App.js
import NavLink from './NavLink'
// ...
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/repos">Repos</NavLink></li>
Oh, how beautiful upon the renders is the composability of components.
06、 URL Params
Consider the following URLs:
/repos/reactjs/react-router
/repos/facebook/react
These URLs would match a route path like this:
/repos/:userName/:repoName
The parts that start with : are URL parameters whose values will be
parsed out and made available to route components on
this.props.params[name].
Adding a Route with Parameters
Let's teach our app how to render screens at /repos/:userName/:repoName.
First we need a component to render at the route, make a new file at
modules/Repo.js that looks something like this:
// modules/Repo.js
import React from 'react'
export default React.createClass({
render() {
return (
<div>
<h2>{this.props.params.repoName}</h2>
</div>
)
}
})
Now open up index.js and add the new route.
// ...
// import Repo
import Repo from './modules/Repo'
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/repos" component={Repos}/>
{/* add the new route */}
<Route path="/repos/:userName/:repoName" component={Repo}/>
<Route path="/about" component={About}/>
</Route>
</Router>
), document.getElementById('app'))
Now we can add some links to this new route in Repos.js.
// Repos.js
import { Link } from 'react-router'
// ...
export default React.createClass({
render() {
return (
<div>
<h2>Repos</h2>
{/* add some links */}
<ul>
<li><Link to="/repos/reactjs/react-router">React Router</Link></li>
<li><Link to="/repos/facebook/react">React</Link></li>
</ul>
</div>
)
}
})
Now go test your links out. Note that the parameter name in the route
path becomes the property name in the component. Both repoName and
userName are available on this.props.params of your component. You
should probably add some prop types to help others and yourself out
later.
07、 More Nesting
Notice how the list of links to different repositories goes away when we
navigate to a repository? What if we want the list to persist, just like
the global navigation persists?
Try to figure that out before reading on.
...
First, nest the Repo route under the Repos route. Then go render
this.props.children in Repos.
// index.js
// ...
<Route path="/repos" component={Repos}>
<Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
// Repos.js
// ...
<div>
<h2>Repos</h2>
<ul>
<li><Link to="/repos/reactjs/react-router">React Router</Link></li>
<li><Link to="/repos/facebook/react">React</Link></li>
</ul>
{/* will render `Repo.js` when at /repos/:userName/:repoName */}
{this.props.children}
</div>
Active Links
Let's bring in our NavLink from before so we can add the active
class name to these links:
// modules/Repos.js
// import it
import NavLink from './NavLink'
// ...
<li><NavLink to="/repos/reactjs/react-router">React Router</NavLink></li>
<li><NavLink to="/repos/facebook/react">React</NavLink></li>
// ...
Notice how both the /repos link up top and the individual repo links are
both active? When child routes are active, so are the parents.
08、 Index Routes
When we visit / in this app it's just our navigation and a blank page.
We'd like to render a Home component there. Lets create a Home
component and then talk about how to render it at /.
// modules/Home.js
import React from 'react'
export default React.createClass({
render() {
return <div>Home</div>
}
})
One option is to see if we have any children in App, and if not,
render Home:
// modules/App.js
import Home from './Home'
// ...
<div>
{/* ... */}
{this.props.children || <Home/>}
</div>
//...
This would work fine, but its likely we'll want Home to be attached to
a route like About and Repos in the future. A few reasons include:
- Participating in a data fetching abstraction that relies on matched
routes and their components. - Participating in
onEnterhooks - Participating in code-splitting
Also, it just feels good to keep App decoupled from Home and let the
route config decide what to render as the children. Remember, we want to
build small apps inside small apps, not big ones!
Let's add a new route to index.js.
// index.js
// new imports:
// add `IndexRoute` to 'react-router' imports
import { Router, Route, hashHistory, IndexRoute } from 'react-router'
// and the Home component
import Home from './modules/Home'
// ...
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
{/* add it here, as a child of `/` */}
<IndexRoute component={Home}/>
<Route path="/repos" component={Repos}>
<Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
<Route path="/about" component={About}/>
</Route>
</Router>
), document.getElementById('app'))
Now open http://localhost:8080 and you'll see the new component is
rendered.
Notice how the IndexRoute has no path. It becomes
this.props.children of the parent when no other child of the parent
matches, or in other words, when the parent's route matches exactly.
Index routes can twist people's brains up sometimes. Hopefully it will
sink in with a bit more time. Just think about a web server that looks
for index.html when you're at /. Same idea, React Router looks for
an index route if a route's path matches exactly.
09、 Index Links
Have you noticed in our app that we don't have any navigation to get
back to rendering the Home component?
Lets add a link to / and see what happens:
// in App.js
// ...
<li><NavLink to="/">Home</NavLink></li>
// ...
Now navigate around. Notice anything weird? The link to Home is always
active! As we learned earlier, parent routes are active when child routes
are active. Unfortunately, / is the parent of everything.
For this link, we want it to only be active when the index route is
active. There are two ways to let the router know you're linking to the
"index route" so it only adds the active class (or styles) when the
index route is rendered.
IndexLink
First let's use the IndexLink instead of NavLink
// App.js
import { IndexLink } from 'react-router'
// ...
<li><IndexLink to="/" activeClassName="active">Home</IndexLink></li>
Fixed! Now this link is only "active" when we're at the index route. Go
ahead and click around to see.
onlyActiveOnIndex Property
We can use Link as well by passing it the onlyActiveOnIndex prop
(IndexLink just wraps Link with this property for convenience).
<li><Link to="/" activeClassName="active" onlyActiveOnIndex={true}>Home</Link></li>
That's fine, but we already abstracted away having to know what the
activeClassName is with Nav.
Remember, in NavLink we're passing along all of our props to Link with
the {...spread} syntax, so we can actually add the prop when we render
a NavLink and it will make its way down to the Link:
<li><NavLink to="/" onlyActiveOnIndex={true}>Home</NavLink></li>