React Training
  • React JS Library
  • Roadmap
  • Training OutLine
  • React js basics
    • Understanding React JS
    • React JS a framework?
    • Setting Up React
    • Say Hello to React
    • Everything is a Component
    • Create-react-app
  • React Building Blocks
    • JSX and Babel
    • One Way Data Flow
    • Virtual DOM
    • V of MVC
    • React Terminology
    • React Tooling
  • Day 01
    • Day 01 OutLine
    • All About JSX
    • React Tools/ npm & webpack
    • Introduction of Webpack
    • Hello world using webpack
      • Webpack Setting up with React
    • NPM Scripts | Package JSON
      • Package.json file
    • React JS Principles
      • One Way Data Flow
      • V of MVC
      • Virtual DOM
    • Create React App - Part-1
    • Create React App - Part-2
  • Day 02
    • Quick Recap
      • Quiz
    • State & Props
      • State & Props in Depth
      • State Vs Props | Compare
    • React LifeCycle Methods
      • React LifeCycle Methods for V-0.16 x
    • Constructor | Lifecycle
    • Write Flicker App | First App
  • Day 03
    • Quick Recap
    • Life Cycle Flow
      • Birth and Mounting
      • Initlization and Construction
      • Pre Mounting
      • Render Method
      • componentDidMount
    • Type of React Components
      • Examples- Quick Compare
      • Class and Functional components
      • Functional Components
    • YouTube application
      • Component Design
    • All in One LifeCycle
  • Assignment
    • React App development
  • Day 04
    • Quick Recap on Lifecycle
    • Lifecycle deprecated/New Methods
      • New Lifecycle Methods
    • Lets Build App Netflix | Mock
  • Assignment
    • Github battle App | Assignment
  • Day 05
    • Quick Recap : Hooks
    • ES6 Features | Hands-on
      • ES6 Code Examples
    • Next Stop - React Router | SPA
      • Code examples | Router
      • React Router Building Blocks
      • Application using react-router-dom
  • Day 06
    • Router V4 | Quick Recap
    • ES2015 | 16 Quick Recap
    • LifeCycle Methods -Part-1
    • LifeCycle Methods -Part-2
  • Day 07
    • Quick Recap | New Lifecycle
    • Quick Recap | React Routing
    • Context API | React JS
      • component with context APIs
      • Context API | examples
    • App using Hooks/Context APIs
  • Assignment
    • Assignments
  • State Management Day-08
    • Quick Recap
    • Managing React State
      • What is Redux
      • Understanding Redux
      • Hello World "Redux"
  • React Redux Day - 09
    • Redux State Manager
    • Redux Redux Development
    • Simple Application | Redux
  • Redux Live Application Day -10
    • Redux with existing Application
      • Redux with React App
      • Lets Build More Apps
      • Should I use Redux from Dan
    • Quick Look at JS in React
    • Learn By Reading
  • miscellaneous Items - Day 11
    • Hooks useReducer
    • Hooks useContext
    • Hooks useRef
    • Hooks useEffect
    • Hooks useState
    • Lazy Loading and code splitting
    • Styling React Component
  • React Next Step - Day 12
    • Topics
    • Jest and Enjyme Testing
    • Examples: Testing
  • React Native
    • What is React Native
    • Setting up Environment
      • Linux Systems
    • React Native Hello World App
    • React Native Architecture
    • React Native Vs Native
    • Expo Cli or React Native CLI
  • React Native core Fundamental
    • React Native "Hello World"
    • Course OutLine
    • Getting started with Expo App
    • Layout with Flexbox
    • Working with Styles and Events
    • Manging Component State and Props
    • Build Simple Task List Application
  • What to Debug & How to Debug
    • Debug React Native Application
Powered by GitBook
On this page

Was this helpful?

  1. Day 06

Router V4 | Quick Recap

React Router 4 is latest version of the official routing library for React. When building Single Page Applications or SPAs, you need client side routing so you can navigate through your React application UI which usually contains multiple pages or views.

React Router 4 allows you to keep your application UI and the URL in synchronization -- so in this section will to quick recap of how to use React Router 4 and what you can achieve with the declarative routing approach.

An example, here's the application in v4:

import { BrowserRouter, Route } from 'react-router-dom'

const PrimaryLayout = () => (
  <div className="primary-layout">
    <header>
      Our React Router 4 App
    </header>
    <main>
      <Route path="/" exact component={HomePage} />
      <Route path="/users" component={UsersPage} />
    </main>
  </div>
)

const HomePage =() => <div>Home Page</div>
const UsersPage = () => <div>Users Page</div>

const App = () => (
  <BrowserRouter>
    <PrimaryLayout />
  </BrowserRouter>
)

render(<App />, document.getElementById('root'))

The first thing that stands out when looking at an app built with React Router v4 is that the "router" seems to be missing. In v3 the router was this giant thing we rendered directly to the DOM which orchestrated our application. Now, besides <BrowserRouter>, the first thing we throw into the DOM is our application itself.

Another v3-staple missing from the v4 example is the use of {props.children} to nest components. This is because in v4, wherever the <Route> component is written is where the sub-component will render to if the route matches.

Inclusive Routing

In the previous example, you may have noticed the exact prop. So what's that all about? V3 routing rules were "exclusive" which meant that only one route would win. V4 routes are "inclusive" by default which means more than one <Route> can match and render at the same time.

In the previous example, we're trying to render either the HomePage or the UsersPage depending on the path. If the exact prop were removed from the example, both the HomePage and UsersPagecomponents would have rendered at the same time when visiting `/users` in the browser.

To demonstrate how inclusive routing is helpful, let's include a UserMenu in the header, but only if we're in the user's part of our application:

const PrimaryLayout = () => (
  <div className="primary-layout">
    <header>
      Our React Router 4 App
      <Route path="/users" component={UsersMenu} />
    </header>
    <main>
      <Route path="/" exact component={HomePage} />
      <Route path="/users" component={UsersPage} />
    </main>
  </div>
)

Now, when the user visits `/users`, both components will render. Something like this was doable in v3 with certain patterns, but it was more difficult. Thanks to v4's inclusive routes, it's now a breeze.

Exclusive Routing

If you need just one route to match in a group, use <Switch> to enable exclusive routing:

const PrimaryLayout = () => (
  <div className="primary-layout">
    <PrimaryHeader />
    <main>
      <Switch>
        <Route path="/" exact component={HomePage} />
        <Route path="/users/add" component={UserAddPage} />
        <Route path="/users" component={UsersPage} />
        <Redirect to="/" />
      </Switch>
    </main>
  </div>
)

Only one of the routes in a given <Switch> will render. We still need exact on the HomePage route though if we're going to list it first. Otherwise the home page route would match when visiting paths like `/users` or `/users/add`. In fact, strategic placement is the name-of-the-game when using an exclusive routing strategy (as it always has been with traditional routers). Notice that we strategically place the routes for /users/add before /users to ensure the correct matching. Since the path /users/addwould match for `/users` and `/users/add`, putting the /users/add first is best.

Sure, we could put them in any order if we use exact in certain ways, but at least we have options.

The <Redirect> component will always do a browser-redirect if encountered, but when it's in a<Switch> statement, the redirect component only gets rendered if no other routes match first. To see how <Redirect> might be used in a non-switch circumstance, see Authorized Route below.

"Index Routes" and "Not Found"

While there is no more <IndexRoute> in v4, using <Route exact> achieves the same thing. Or if no routes resolved, then use <Switch> with <Redirect> to redirect to a default page with a valid path (as I did with HomePage in the example), or even a not-found page.

Nested Layouts

You're probably starting to anticipate nested sub layouts and how you might achieve them. I didn't think I would struggle with this concept, but I did. React Router v4 gives us a lot of options, which makes it powerful. Options, though, means the freedom to choose strategies that are not ideal. On the surface, nested layouts are trivial, but depending on your choices you may experience friction because of the way you organized the router.

To demonstrate, let's imagine that we want to expand our users section so we have a "browse users" page and a "user profile" page. We also want similar pages for products. Users and products both need sub-layout that are special and unique to each respective section. For example, each might have different navigation tabs. There are a few approaches to solve this, some good and some bad. The first approach is not very good but I want to show you so you don't fall into this trap. The second approach is much better.

For the first, let's modify our PrimaryLayout to accommodate the browsing and profile pages for users and products:

const PrimaryLayout = props => {
  return (
    <div className="primary-layout">
      <PrimaryHeader />
      <main>
        <Switch>
          <Route path="/" exact component={HomePage} />
          <Route path="/users" exact component={BrowseUsersPage} />
          <Route path="/users/:userId" component={UserProfilePage} />
          <Route path="/products" exact component={BrowseProductsPage} />
          <Route path="/products/:productId" component={ProductProfilePage} />
          <Redirect to="/" />
        </Switch>
      </main>
    </div>
  )
}

While this does technically work, taking a closer look at the two user pages starts to reveal the problem:

const BrowseUsersPage = () => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <BrowseUserTable />
    </div>
  </div>
)

const UserProfilePage = props => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <UserProfile userId={props.match.params.userId} />
    </div>
  </div>
)

Each user page not only renders its respective content but also has to be concerned with the sub layout itself (and the sub layout is repeated for each). While this example is small and might seem trivial, repeated code can be a problem in a real application. Not to mention, each time a BrowseUsersPage or UserProfilePage is rendered, it will create a new instance of UserNav which means all of its lifecycle methods start over. Had the navigation tabs required initial network traffic, this would cause unnecessary requests — all because of how we decided to use the router.

Here's a different approach which is better:

const PrimaryLayout = props => {
  return (
    <div className="primary-layout">
      <PrimaryHeader />
      <main>
        <Switch>
          <Route path="/" exact component={HomePage} />
          <Route path="/users" component={UserSubLayout} />
          <Route path="/products" component={ProductSubLayout} />
          <Redirect to="/" />
        </Switch>
      </main>
    </div>
  )
}

Instead of four routes corresponding to each of the user's and product's pages, we have two routes for each section's layout instead.

Notice the above routes do not use the exact prop anymore because we want /users to match any route that starts with /users and similarly for products.

With this strategy, it becomes the task of the sub layouts to render additional routes. Here's what the UserSubLayout could look like:

const UserSubLayout = () => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <Switch>
        <Route path="/users" exact component={BrowseUsersPage} />
        <Route path="/users/:userId" component={UserProfilePage} />
      </Switch>
    </div>
  </div>
)

The most obvious win in the new strategy is that the layout isn't repeated among all the user pages. It's a double win too because it won't have the same lifecycle problems as with the first example.

One thing to notice is that even though we're deeply nested in our layout structure, the routes still need to identify their full path in order to match. To save yourself the repetitive typing (and in case you decide to change the word "users" to something else), use props.match.path instead:

const UserSubLayout = props => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <Switch>
        <Route path={props.match.path} exact component={BrowseUsersPage} />
        <Route path={`${props.match.path}/:userId`} component={UserProfilePage} />
      </Switch>
    </div>
  </div>
)

Match

match.path vs match.url

The differences between these two can seem unclear at first. Console logging them can sometimes reveal the same output making their differences even more unclear. For example, both these console logs will output the same value when the browser path is `/users`:

const UserSubLayout = ({ match }) => {
  console.log(match.url)   // output: "/users"
  console.log(match.path)  // output: "/users"
  return (
    <div className="user-sub-layout">
      <aside>
        <UserNav />
      </aside>
      <div className="primary-content">
        <Switch>
          <Route path={match.path} exact component={BrowseUsersPage} />
          <Route path={`${match.path}/:userId`} component={UserProfilePage} />
        </Switch>
      </div>
    </div>
  )
}

While we can't see the difference yet, match.url is the actual path in the browser URL and match.path is the path written for the router. This is why they are the same, at least so far. However, if we did the same console logs one level deeper in UserProfilePage and visit `/users/5` in the browser, match.url would be "/users/5" and match.path would be "/users/:userId".

Which to choose?

If you're going to use one of these to help build your route paths, I urge you to choose match.path. Using match.url to build route paths will eventually lead a scenario that you don't want. Here's a scenario which happened to me. Inside a component like UserProfilePage (which is rendered when the user visits `/users/5`), I rendered sub components like these:

const UserComments = ({ match }) => (
  <div>UserId: {match.params.userId}</div>
)

const UserSettings = ({ match }) => (
  <div>UserId: {match.params.userId}</div>
)

const UserProfilePage = ({ match }) => (
  <div>
    User Profile:
    <Route path={`${match.url}/comments`} component={UserComments} />
    <Route path={`${match.path}/settings`} component={UserSettings} />
  </div>
)

To illustrate the problem, I'm rendering two sub components with one route path being made from match.url and one from match.path. Here's what happens when visiting these pages in the browser:

  • Visiting `/users/5/comments` renders "UserId: undefined".

  • Visiting `/users/5/settings` renders "UserId: 5".

So why does match.path work for helping to build our paths and match.url doesn't? The answer lies in the fact that {${match.url}/comments} is basically the same thing as if I had hard-coded {'/users/5/comments'}. Doing this means the subsequent component won't be able to fill match.params correctly because there were no params in the path, only a hardcoded 5.

match:

  • path - (string) The path pattern used to match. Useful for building nested <Route>s

  • url - (string) The matched portion of the URL. Useful for building nested <Link>s

Let's assume the app we're making is a dashboard so we want to be able to add and edit users by visiting `/users/add` and `/users/5/edit`. But with the previous examples, users/:userId already points to a UserProfilePage. So does that mean that the route with users/:userId now needs to point to yet another sub-sub-layout to accomodate editing and the profile? I don't think so. Since both the edit and profile pages share the same user-sub-layout, this strategy works out fine:

const UserSubLayout = ({ match }) => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <Switch>
        <Route exact path={props.match.path} component={BrowseUsersPage} />
        <Route path={`${match.path}/add`} component={AddUserPage} />
        <Route path={`${match.path}/:userId/edit`} component={EditUserPage} />
        <Route path={`${match.path}/:userId`} component={UserProfilePage} />
      </Switch>
    </div>
  </div>
)

Notice that the add and edit routes strategically come before the profile route to ensure there the proper matching. Had the profile path been first, visiting `/users/add` would have matched the profile (because "add" would have matched the :userId.

Authorized Route

It's very common in applications to restrict the user's ability to visit certain routes depending on their login status. Also common is to have a "look-and-feel" for the unauthorized pages (like "log in" and "forgot password") vs the "look-and-feel" for the authorized ones (the main part of the application). To solve each of these needs, consider this main entry point to an application:

class App extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <BrowserRouter>
          <Switch>
            <Route path="/auth" component={UnauthorizedLayout} />
            <AuthorizedRoute path="/app" component={PrimaryLayout} />
          </Switch>
        </BrowserRouter>
      </Provider>
    )
  }
}

There are a few takeaways with this approach. The first being that I'm choosing between two top-level layouts depending on which section of the application we're in. Visiting paths like `/auth/login` or `/auth/forgot-password` will utilize the UnauthorizedLayout — one that looks appropriate for those contexts. When the user is logged in, we'll ensure all paths have an `/app` prefix which uses AuthorizedRoute to determine if the user is logged in or not. If the user tries to visit a page starting with `/app` and they aren't logged in, they will be redirected to the login page.

class AuthorizedRoute extends React.Component {
  componentWillMount() {
    getLoggedUser()
  }

  render() {
    const { component: Component, pending, logged, ...rest } = this.props
    return (
      <Route {...rest} render={props => {
        if (pending) return <div>Loading...</div>
        return logged
          ? <Component {...this.props} />
          : <Redirect to="/auth/login" />
      }} />
    )
  }
}

const stateToProps = ({ loggedUserState }) => ({
  pending: loggedUserState.pending,
  logged: loggedUserState.logged
})

export default connect(stateToProps)(AuthorizedRoute)

While your login strategy might differ from mine, I use a network request to getLoggedUser() and plug pending and logged into Redux state. pending just means the request is still in route.

Other mentions

There's a lot of other cool aspects React Router v4. To wrap up though, let's be sure to mention a few small things so they don't catch you off guard.

#<Link> vs <NavLink>

const PrimaryHeader = () => (
  <header className="primary-header">
    <h1>Welcome to our app!</h1>
    <nav>
      <NavLink to="/app" exact activeClassName="active">Home</NavLink>
      <NavLink to="/app/users" activeClassName="active">Users</NavLink>
      <NavLink to="/app/products" activeClassName="active">Products</NavLink>
    </nav>
  </header>
)

The use of <NavLink> allows me to set a class of active to whichever link is active. But also, notice that I can use exact on these as well. Without exact the home page link would be active when visiting `/app/users` because of the inclusive matching strategies of v4. In my personal experiences, <NavLink> with the option of exact is a lot more stable than the v3 <Link> equivalent.

PreviousApplication using react-router-domNextES2015 | 16 Quick Recap

Last updated 5 years ago

Was this helpful?

New API Concept: Since our app is meant for the browser, we need to wrap it in <BrowserRouter> which comes from v4. Also notice we import from react-router-domnow (which means we npm install react-router-dom not react-router). Hint! It's called react-router-dom now because there's also a .

To understand the matching logic better, review which is what v4 now uses to determine whether routes match the URL.

New API Concept: props.match is given to any component rendered by <Route>. As you can see, the userId is provided by props.match.params. See more in . Alternatively, if any component needs access to props.match but the component wasn't rendered by a <Route> directly, we can use the Higher Order Component.

As we've seen so far, props.match is useful for knowing what userId the profile is rendering and also for writing our routes. The match object gives us several properties including match.params, match.path, match.url and .

ES2015 Concept: match is being at the parameter level of the component function. This means we can type match.path instead of props.match.path.

It wasn't until later that I saw and realized how important it was:

Avoiding Match Collisions

Alternatively, we can put the profile route first if we make the path ${match.path}/:userId(\\d+)which ensures that :userId must be a number. Then visiting `/users/add` wouldn't create a conflict. I learned this trick in the docs for .

Using works very similarly with React Router v4 as it did before, simply wrap <BrowserRouter> in <Provider> and it's all set.

AuthorizedRoute isn't a part of v4 though. I made it myself . One amazing new feature in v4 is the ability to create your own routes for specialized purposes. Instead of passing a component prop into <Route>, pass a render callback instead:

Click here to see a fully working .

In v4, there are two ways to integrate an anchor tag with the router: and

<NavLink> works the same as <Link> but gives you some extra styling abilities depending on if the <NavLink> matches the browser's URL. For instance, in the , there is a <PrimaryHeader> component that looks like this:

native version
path-to-regexp
v4 documentation
withRouter()
several more
destructured
this part of the documentation
#
path-to-regexp
react-redux
with the help of v4 docs
Authentication Example at CodePen
<Link>
<NavLink>
example application