Nested routes

OK, we have done with creating layout specific components, but we still need to look at how we can create nested routes for them so that the components are passed into parents with props. This is important, so as to allow a level of dynamism within our EIS application. Here is our HTML, showing what it looks like now:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>React Router - Sample application with bootstrap</title>
        <link rel="stylesheet" href="css/bootstrap.min.css">
        <link rel="stylesheet" href="css/font-awesome.min.css">
        <link rel="stylesheet" href="css/custom.css">
    </head>
    <body>
        <div id="nav"></div>
        <div id="reactapp"></div>
        <script type="text/javascript" src="js/react.js"></script>
        <script type="text/javascript" src="js/react-dom.min.js"></script>
        <script src="js/browser.min.js"></script>
        <script src="js/jquery-1.10.2.min.js"></script>
        <script src="js/bootstrap.min.js"></script>
        <script src="https://unpkg.com/react-router/umd/
        ReactRouter.min.js"></script>
        <script src="components/bootstrap-navbar.js" 
        type="text/babel"></script>
        <script src="components/router.js" type="text/babel"></script>    
    </body>
</html>  

Let's take a look at the router which we created earlier once again:

ReactDOM.render((
    <Router>
        <Route path="/" component={PageLayout}>
            <IndexRoute component={TwoColumnLayout}/>
            <Route path="/edit" component={Edit} />
            <Route path="/alltickets" component={allTickets} />
            <Route path="/newticket" component={addNewTicket} />
        </Route>
    </Router>
), document.getElementById('reactapp'));

So now we have added the extra element, <IndexRoute />, to the mapping with our parent, setting its view to be our {TwoColumnLayout} component. The IndexRoute element is responsible for which component is being displayed when our app initially loads.

Don't forget to wrap inside the {PageLayout} component. We can also define the path rule on <indexRoute>, the same as <Route>:

ReactDOM.render((
<Router>
    <Route component={PageLayout}>
        <IndexRoute path="/" component={TwoColumnLayout}/>
        <Route path="/edit" component={Edit} />
        <Route path="/alltickets" component={allTickets} />
        <Route path="/newticket" component={addNewTicket} />
    </Route>
</Router>
), document.getElementById('reactapp'));

Observe the following screenshot:

Nested routes

That looks good. As mentioned in our <IndexRoute>, it always loads the <TwoColumnLayout> on the first page load. Now let's navigate and take a look at some other pages.

React also provides us with a way to redirect the route using the <IndexRedirect> component:

<Route path="/" component={App}> 
    <IndexRedirect to="/welcome" /> 
    <Route path="welcome" component={Welcome} /> 
    <Route path="profile" component={profile} /> 
</Route> 

Observe the following screenshot:

Nested routes

You'll have noticed that I've clicked on the Edit Profile page and it rendered the edit page component but it didn't add the active class on the current active link. For this we need to replace the <a> tag with the React <Link> tag.

React router

The React router used the <link> component instead of the <a> element which we have used in nav. It's necessary to use this if we are working with the React router. Let's add <link> in our navigation instead of the <a> tag and replace the href attribute with two.

The <a> tag:

<li className="active"><a href="#/">Home</a></li> 

Replace this with:

<li className="active"><Link to="#/">Home</Link></li> 

Let's take a look in the browser to see the behavior of <link>:

React router

It's showing an error in the console because we have not added the Link component reference in the ReactRouter object:

var { Router, Route, IndexRoute, IndexLink, Link, browserHistory } = ReactRouter 

We have also added the browserHistory object, which we'll explain later.

Here is what our PageLayout component looks like:

var PageLayout = React.createClass({
render: function() {
return ( 
<main>
    <div className="navbar navbar-default navbar-static-top"
    role="navigation">
        <div className="container">
            <div className="navbar-header">
                <button type="button" className="navbar-toggle"
                data-toggle="collapse"
                data-target=".navbar-collapse">
                <span className="sr-only">Toggle navigation</span>
                <span className="icon-bar"></span>
                <span className="icon-bar"></span>
                <span className="icon-bar"></span>
                </button>
                <Link className="navbar-brand" to="/">
                EIS</Link>
            </div>
            <div className="navbar-collapse collapse">
                <ul className="nav navbar-nav">
                    <li className="active">
                        <IndexLink activeClassName="active" to="/">
                        Home</IndexLink>
                    </li>
                    <li>
                        <Link to="/edit" activeClassName="active">
                        Edit Profile</Link>
                    </li>
                    <li className="dropdown">
                        <Link to="#" className="dropdown-toggle"
                        data-toggle="dropdown">
                        Help Desk <b className="caret"></b></Link>
                        <ul className="dropdown-menu">
                            <li>
                                <Link to="/alltickets">
                                View Tickets</Link>
                            </li>
                            <li>
                                <Link to="/newticket">
                                New Ticket</Link>
                            </li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </div>
    <div className="container">
        <h1>Welcome to EIS</h1>
        <hr/>
        <div className="row">
            <div className="col-md-12 col-lg-12">
                {this.props.children}
            </div>
        </div>
    </div>
</main>
)
}
})

To activate the default link, we've used <IndexRoute>. This automatically defines the default link's active class. The activeClassName attribute will match the URL with the to value and add the active class to it. If we do not use activeClassName then it cannot add the class automatically on the active link. Let's take a quick look at the browser:

React router

It's working as expected. Let's take a look at the DOM HTML in the console:

React router

We just need to overwrite the Bootstrap default style on <li> .active to <a>:

.navbar-default .navbar-nav li>.active, .navbar-default
.navbar-nav li>.active:hover, .navbar-default 
.navbar-nav li>.active:focus { 
    color: #555; 
    background-color: #e7e7e7; 
}  

We can also pass a parameter in the route to match, validate, and render the UI:

<Link to={`/tickets/${ticket.id}`}>View Tickets</Link>

And in the router we need to add:

<Route path="tickets/:ticketId" component={ticketDetail} /> 

We can add as many parameters as required, and it's easy to pull these out in our component. We'll have access to all the route parameters as objects.

The React router supports the IE9+ browser version but for IE8 you can use the Node npm package, react-router-ie8

NotFoundRoute

The React router also provides a way to show a 404 error on the client side if the path is not matched with the route:

var NoMatch = React.createClass({ 
   render: function() { 
       return (<h1>URL not Found</h1>); 
   } 
}); 
 
<Route path="*" component={NoMatch}/>

Observe the following screenshot:

NotFoundRoute

It's amazing how easily we can handle the unmatched URL.

Here is what our router looks like:

ReactDOM.render(( 
    <Router> 
        <Route path="/" component={PageLayout}> 
            <IndexRoute component={TwoColumnLayout}/> 
                <Route path="/edit" component={Edit} /> 
                <Route path="/alltickets" component={allTickets} /> 
                <Route path="/newticket" component={addNewTicket} /> 
                </Route> 
        <Route path="*" component={NoMatch}/> 
    </Router> 
), document.getElementById('reactapp')); 

Here is the list of other link attributes that we can use:

  •  activeStyle: We can use this for the custom inline style. For example:
        <Link activeStyle={{color:'#53acff'}} to='/'>Home</Link>
  • onlyActiveOnIndex : We can use this attribute when we add a custom inline style with the activeStyle attribute. It will apply only when we are on an exact link. For example:
        <Link onlyActiveOnIndex activeStyle={{color:'#53acff'}} 
        to='/'>Home</Link>

Browser history

One more cool feature of the React router is that it uses the browserHistory API to manipulate the URL and create a clean URL.

With the default hashHistory:

  http://localhost:9090/react/chapter7/#/?_k=j8dlzv
  http://localhost:9090/react/chapter7/#/edit?_k=yqdzh0 http://localhost:9090/react/chapter7/#/alltickets?_k=0zc49r
  http://localhost:9090/react/chapter7/#/newticket?_k=vx8e8c

When we use the browserHistory in our app, the URL will look clean:

 http://localhost:9090/react/chapter7/
 http://localhost:9090/react/chapter7/edit
 http://localhost:9090/react/chapter7/alltickets
 http://localhost:9090/react/chapter7/newticket

The URL now looks clean and user friendly.

Query string parameters

We can also pass query strings as props to any component that will be rendered at a specific route. For accessing these prop parameters, we need to add the props.location.query property in our component.

To see how this works, let's create a new component called RouteQueryString:

var QueryRoute = React.createClass({ 
    render: function(props) { 
        return (<h2>{this.props.location.query.message}</h2>); 
        // Using this we can read the parameters from the 
        request which are visible in the URL's
    } 
}); 
<IndexLink activeClassName='active' to= 
     {{ pathname: '/query', query: { message: 'Hello from Route Query' } }}> 
         Route Query 
</IndexLink> 

Include this route path in the router:

<Route path='/query' component={QueryRoute} /> 

Let's see the output in the browser:

Query string parameters

Great, it's working as expected.

Here is what our Router configuration looks like now:

ReactDOM.render((
    <Router>
        <Route path="/" component={PageLayout}>
            <IndexRoute component={TwoColumnLayout}/>
            <Route path="/edit" component={Edit} />
            <Route path="/alltickets" component={allTickets} />
            <Route path="/newticket" component={addNewTicket} />
            <Route path='/query' component={QueryRoute} />
        </Route>
        <Route path="*" component={NoMatch}/>
    </Router>
), document.getElementById('reactapp'));

Customizing your history further

If we want to customize the history options or use other enhancers from history, then we need to use the useRouterHistory component of React.

useRouterHistory already pre-enhances from the history factory with the useQueries and useBasename from history. Examples include:

import { useRouterHistory } from 'react-router' 
import { createHistory } from 'history' 
const history = useRouterHistory(createHistory)({ 
    basename: '/base-path' 
}) 

Using the useBeforeUnload enhancer:

import { useRouterHistory } from 'react-router' 
import { createHistory,useBeforeUnload } from 'history' 
const history = useRouterHistory(useBeforeUnload(createHistory))() 
history.listenBeforeUnload(function () { 
    return 'Are you sure you want to reload this page?' 
}) 

Before using the React router, we must be aware of React router version updates.

Please visit this link https://github.com/ReactTraining/react-router/blob/master/upgrade-guides/v2.0.0.md to be updated.

Here is the short list of deprecated syntax in the router:

<Route name="" /> is deprecated. Use <Route path="" /> instead. 
<Route handler="" /> is deprecated. Use <Route component="" /> instead. 
<NotFoundRoute /> is deprecated. See Alternative 
<RouteHandler /> is deprecated. 
willTransitionTo is deprecated. See onEnter 
willTransitionFrom is deprecated. See onLeave 
query={{ the: 'query' }} is deprecated. Use to={{ pathname: '/foo', query: { the: 'query' } }} 

history.isActive is replaced with router.isActive.

RoutingContext is renamed RouterContext.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset