React Router V4 - Tuong-Nguyen/JavaScript-Structure GitHub Wiki
React Router V4 has been broken three packages: react-router, react-router-dom, react-router-native.
For web development, we should use react-router-dom.
Using npm:
$ npm install --save react-router-dom
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect,
Prompt,
NavLink} from 'react-router-dom';const Home = () => <h1>Home</h1>
const About = (props) => {
console.log(props);
return(
<h1>About</h1>
)
}
const Links = () => (
<nav>
<Link to="/">Home</Link>
<Link to={{pathname: '/about'}}>About</Link>
<Link replace to="/contact">Contact</Link>
<Link to="/content">Content</Link>
</nav>
)
const App = () => (
<Router>
<div>
<Links />
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" render={() => <h1>Contact</h1>} />
<Route path="/content" children={() => <h1>Content</h1>} />
</div>
</Router>
)
ReactDOM.render(<App />, document.getElementById('root'));The example above uses essential components for routing as following:
<Link> : help to navigate between routes.
<Router> as <BrowserRouter> (for browser based projects): keeps track of the current location to keep UI in sync with the URL.
<Route>: renders some UI when the location's pathname ( a part of URL) matches with the route's path.
There are three ways to render something with a <Route>:
<Route component> : a React component.
<Route render>: allows for inline rendering by passing in a function.
<Route children>: likes render props but this will always be rendered, regardless of whether the route's path matches the current location
The element rendered be the will be passed a number of props. These will be:
-
match: object -
location: object -
history: object
The props of About component in the example above will includes these objects.
Example for an URL matches a route:
URL: http://localhost:3000/about
Location's pathname or location.pathname : /about
Route with path /about will render About component.
exact: bool
When true, will only match if the location.pathname matches the route's path exactly.
<Route exact path="/about" component={About} />Setting exact as above means it is true.
| location.pathname | route's path | exact | matches |
|---|---|---|---|
| /about | /about | true | yes |
| /about/ | /about | true | yes |
| /about/abc | /about | true | no |
| /about/abc | /about | false | yes |
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />With the URL's pathname: /about
- The exact is false, both Home and About components will be rendered.
- The exact is true, About components will only be rendered.
strict: bool
When true, a path that has a trailing slash will only match a location.pathname with a trailing slash
<Route strict path="/about/" component={About} />| location.pathname | route's path | strict | matches |
|---|---|---|---|
| /about | /about/ | true | no |
| /about/ | /about/ | true | yes |
| /about/abc | /about | true | yes |
- Setting both
strictandexactare true to enforce that alocation.pathnamehas no trailing slash.
<Route exact strict path="/about" component={About} />| location.pathname | route's path | matches |
|---|---|---|
| /about | /about | yes |
| /about/ | /about | no |
| /about/abc | /about | no |
A <Route> that doesn't define a path or uses children method to render will matches with any URL's pathname.
<Route exact path="/" render={() => <h1>Home</h1>} />
<Route path="/about" render={() => <h1>About</h1>} />
<Route render={() => <h1>Page not found</h1>} />As example above, the ` that has 'Page not found' content will always be rendered.
| location.pathname | matched routes | render |
|---|---|---|
| / | 1, 3 | Home and Page not found |
| /about | 2, 3 | About and Page not found |
| /abc | 3 | Page not found |
Using <Switch> component to only render the first matched route.
<Switch>
<Route exact path="/" render={() => <h1>Home</h1>} />
<Route path="/about" render={() => <h1>About</h1>} />
<Route render={() => <h1>Page not found</h1>} />
</Switch>| location.pathname | matched routes | render |
|---|---|---|
| / | 1 | Home |
| /about | 2 | About |
| /abc | 3 | Page not found |
<NavLink> is similar to <Link> but allow to set styling attribute to the <NavLink> element activated or matches with current URL.
<nav>
<NavLink exact activeClassName="active" to="/">Home</NavLink>
<NavLink activeStyle={{color: 'green'}} to="/about">About</NavLink>
<NavLink activeClassName="gold"
isActive={activeFunc}
to="/contact/:id">Contact</NavLink>
<NavLink to="/contact/100">ID Link</NavLink>
</nav>CSS file
a {
margin: 5px;
}
a, a:visited{
color: blue;
}
a.active {
color: red;
}
a.gold {
color: gold;
}There are two way to set styling attribute for `:
- activeClassName: class
- activeStyle: styling object
By default, when a <NavLink> is active, the class active is apply automatically.
We don't need declare activeClassName="active" as example above.
Declaring activeClassName="anotherClass" if we would like to apply another class as the third <NavLink>.
Add a function to <NavLink> to add extra logic for determining whether the link is active by isActive props.
const activeFunc= (match, location) => {
if (!match) {
return false;
}
//check id param from URL's pathname
const ID = parseInt(match.params.id)
return !isNaN(eventID) && eventID % 2 === 1
}when any <NavLink> is active, the function above will be executed to check whether the '` that is set this function is active.
Creating nested routes by define child routes within the component of parent route.
const Home = () => (<h1>Home</h1>)
const Menu = (props) => {
return (
<div>
<h1>Menu</h1>
<Link to="/menu/food">Food</Link>
<Link to="/menu/drink">Drinks</Link>
<Link to="/menu/sides">Sides</Link>
<Route path={`${props.match.url}/:section`}
render={({match}) => <h2>{match.params.section}</h2>}/>
</div>)
}
const NestedRoutesApp = (props) => (
<Router>
<div>
<Link to="/">Home</Link>
<Link to="/menu">Menu</Link>
<Route exact path="/" component={Home} />
<Route path="/menu" component={Menu} />
</div>
</Router>
)Using props.match.url to get matched URL's pathname of the parent route to make a relative URL.
Using `' to prompt user before navigating away from a page.
const Home = () => (<h1>Home</h1>)
class Form extends React.Component {
state = {dirty: false}
setDirty = () => this.setState({dirty: true})
render(){
return (
<div>
<h1>Form</h1>
<input type="text" onInput={this.setDirty}/>
<Prompt
when={this.state.dirty}
message="Data will be lost"
/>
</div>
)
}
}
const PromptApp = () => (
<Router>
<div>
<Link to="/">Home</Link>
<Link to="/form">Form</Link>
<Route exact path="/" component={Home} />
<Route path="/form" component={Form} />
</div>
</Router>
)As example above, a confirmation box will show to user when having a dirty form and then navigating away Form page.
<Prompt> has two the following props:
When: bool - a condition to render <Prompt>.
message: string or func - a string or function returns a string to show a prompt to user.
Add query parameters within URL's pathname by the following two ways:
- Inline
<Link to="/?id=123&name=abc">to='/?id=123&name=abc'</Link>Parameters string will start with question mark.
- Location object
<Link to={{pathname: '/', search: 'id=456&name=def'}}>to={location}</Link>Get the value of query parametes by using URLSearchParams() class.
const QueryParametersApp = (props) => (
<Router>
<div>
<Links />
<Route path="/" render={({match, location}) => (
<div>
<p>{new URLSearchParams(location.search).get('id')}</p>
<p>{new URLSearchParams(location.search).get('name')}</p>
</div>
)} />
</div>
</Router>
)Define route's path to matches with URL parameters as following:
- Optional
<Route path="/:page?/:subpage?" render={({match}) => (
<h1>
PAGE: {match.params.page || 'Home' }<br />
SUBPAGE: {match.params.subpage}<br />
</h1>
)} />A parameter will start with a colon and end up with a slash. The URL parameters is optional when it ends with question mark.
- Required
<Route path="/:page/:subpage" render={({match}) => (
<h1>
PAGE: {match.params.page || 'Home' }<br />
SUBPAGE: {match.params.subpage}<br />
</h1>
)} />The route will not match with URL's pathname that doesn't have two parameters as above.
We could define a route with the path is regular expression.
const Links = () => (
<nav>
<Link to="/">/</Link>
<Link to="/17-08-2017/.html">/17-08-2017/.html</Link>
<Link to="/1-1-2017/.html">/1-1-2017/.html</Link>
</nav>
)
const regexPath = "/:a(\d{2}-\d\{2}-\d{4})/:b(\.[a-z]+)"
const RegexRouteApp = (props) => (
<Router>
<div>
<Links />
<h2>Route path: {regexPath}</h2>
<Route path="/:a(\d{2}-\d{2}-\d{4})/:b(\.[a-z]+)" render={({match}) => (
<h1>
ParamA: {match.params.a}<br />
ParamB: {match.params.b}<br />
</h1>
)} />
</div>
</Router>
)The URL parameters will matches with the regex in parenthesis and get it's value by the name of param is front of.
A route redirects to another route:
<Route path="/new/:str"
render={({match}) => (<h1>New: {match.params.str}</h1>)} />
<Route path="/old/:str" render={({match}) => (
<Redirect to={`/new/${match.params.str}`}/>
)} />Redirect a link that isn't defined route to another route.
const Links = () =>
<nav>
<Link to="/">Home</Link>
<Link to="/old">/old : this link is not defined route</Link>
<Link to="/new">New</Link>
</nav>
const RedirectSwitchApp = () => (
<Router>
<div>
<Links />
<Switch>
<Route exact path="/" render={() => (<h1>Home</h1>)} />
<Route path="/new" render={() => (<h1>New</h1>)} />
<Redirect from="/old" to="/new" />
</Switch>
</div>
</Router>
)We could remove from props from <Redirect> to redirect all links that aren't defined routes as following:
<Switch>
<Route exact path="/" render={() => (<h1>Home</h1>)} />
<Route path="/new" render={() => (<h1>New</h1>)} />
<Redirect to="/new" />
</Switch><Redirect> should be the last element in <Switch>.
Demo branch Git: React-RouterV4/nkim
Folder: react-router
Run storybook to show demo.