CHAPTER 5

image

Routing

The URL is an important strength that the Web has over native apps. It was born as simple pointer to a document on a server, but in web applications the best way to think of it is as the representation of the application’s current state. By looking at the URL the user can understand the part of the application where he currently is, but he can also copy it for later use or pass it along.

Implementing Routing the “Naive” Way

To understand how basic routing works as well as the complications that quickly arise in scenarios a little bigger than basic, non-nested navigation, let’s begin by implementing a simple component that, depending on the current URL, renders a different child component. You’ll create an application that will use the GitHub API to return the list of repositories for the pro React user. Besides this “repositories” section, the application will also have a home page and an About section. Let’s focus on the main component and the routing code, shown in Listing 5-1.

The code is pretty straightforward. On the component constructor, you get the current hash location of the URL and assign it to the route state. For simplicity purposes, you will not be dealing with HTML5 URL History API for now. Then, when the component mounts, you add an event listener, so every time the URL changes the route state will be updated and the component will render again. Speaking of rendering, all you need to do in the render method is use the appropriate component based on the current route, as shown in Listing 5-2.

In this simple example, all child components that represent internal navigation pages have this same structure (but with different headings), as shown in Listings 5-3 through 5-5.

The routing system already works, and if you throw in some styling, it can look like Figure 5-1. The sample CSS used in this case is shown in Listing 5-6.

9781484212615_Fig05-01.jpg

Figure 5-1. Sample routing

Although it works for this sample scenario case, there are at least two concerns with this approach, one more conceptual and one more practical:

  • In this sample implementation, URL maintenance has taken center stage: instead of automatically updating the URL while the application state flows programmatically, you’re directly listening and manipulating the URL to get the app to a different state.
  • The routing code can grow exponentially in complexity in non-trivial scenarios. Imagine for example that inside the Repos page you can see a list of repos for the pro React user on GitHub, with internal routes for repository details, something like /repos/repo_id (as illustrated in Figure 5-2).

    9781484212615_Fig05-02.jpg

    Figure 5-2. Illustrating nested routes

  • You’d have to make your URL parsing a lot more intelligently, and end up with a lot of code to figure out which branch of nested components to be rendered at any given URL.

For scenarios more complex than a single-level, basic routing, the recommended approach is to use the React Router library. Nested URLs and nested component hierarchy are at the heart of React Router’s declarative API, and despite not being part of the React core, it’s well regarded by the React community as the standard library for the matter.

React Router

React Router is the most popular solution for adding routing to a React application. It keeps the UI in sync with the URL by having components associated with routes (at any nesting level). When the user changes the URL, components get unmounted and mounted automatically. Another advantage of the React Router library is that it provides mechanisms so that you can control the flow of your application without different entry points depending whether the user entered a state programmatically or by hitting a new URL: the code that runs in any case is the same.

Since React Router is an external library, it must be installed with npm (along with the History library, which is a React Router peer dependency). To install version 1 of both libraries, use npm install --save [email protected] [email protected].

React Router provides three components to get started:

  • Router and Route: Used to declaratively map routes to your application’s screen hierarchy.
  • Link: Used to create a fully accessible anchor tag with the proper href. Of course this isn’t the only way to navigate the project, but usually it’s the main form the end user will interact with.

Let’s change the first example from a “naive” implementation to using React Router. Once installed, begin by making the appropriate imports on your App component, as show in Listing 5-7.

Inside the class, you can get rid of the constructor and componentDidMount methods that you used to manage URL parsing and event listening; this will be automatically taken of care now. Inside the render method, you can also get rid of the switch statement; the React Router will automatically set the children props to whichever is the appropriate component based on the current route. Notice, also, that you need to replace <a> tags for <Link> components to generate the suitable navigation links. Listing 5-8 shows the updated App component’s class.

Finally, you need to declare your routes. You do this at the end of the file. Instead of rendering the App component to the DOM, you pass the Router component with some routes to React DOM render method, as shown in Listing 5-9.

The complete code for the App.js file is shown in Listing 5-10.

Image Tip  Named Components: Usually a route has a single component, which is made available through this.props.children on the parent component. It’s also possible to declare one or more named components when setting the route. In this case, the components will be made available to their parent by name on props.children. Example:

React.render((
  <Router>
    <Route path="/" component={App}>
      <Route path="groups" components={{content: Groups, sidebar: GroupsSidebar}}/>
      <Route path="users" components={{content: Users, sidebar: UsersSidebar}}/>
    </Route>
  </Router>
), element);

Then, in the component:

render() {
  return (
    <div>
      {this.props.children.sidebar}-{this.props.children.content}
    </div>
  );
}

Index Route

If you test right now, you will see that everything works as expected. But there is a difference from the original implementation. You’re not showing the Home component in any route anymore. If you hit the server on the “/” route, it renders the App component without any children, as shown in Figure 5-3.

9781484212615_Fig05-03.jpg

Figure 5-3. Home route renders the App component with no children

The first thing that may come to mind is to simply add a new Home route the router, but which path are you going to use?

<Router>
  <Route path="/" component={App}>
    <Route path="???" component={Home}/>
    <Route path="about" component={About}/>
    <Route path="repos" component={Repos}/>
  </Route>
</Router>

Instead, you can use an <IndexRoute> for this. Just import the additional component and use it to configure the index route, as shown here and in Figure 5-4.

import React, { Component } from ’react’;
import { render } from ’react-dom’;

import { Router, Route, IndexRoute, Link } from ’react-router’;

import About from ’./About’;
import Repos from ’./Repos’;
import Home from ’./Home’;

class App extends Component {...}

render((
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Home}/>
      <Route path="about" component={About} />
      <Route path="repos" component={Repos} />
    </Route>
  </Router>
), document.getElementById(’root’));

9781484212615_Fig05-04.jpg

Figure 5-4. If no route is provided, the home component is rendered

Routes with Parameters

Now that you have an implementation on par with your original “naive” routing, let’s expand on it to actually fetch data from the GitHub API in the Repos component. You won’t do anything new here: you will create a local state for the repositories and fetch the API from the componentDidMount lifecycle method, as you did in earlier examples. Listing 5-11 shows the Repo component with the additional fetching.

Image Note  In this sample code, you use the new window.fetch function, as you did in earlier examples of this book. Since older browsers don’t have support for the new standard, make sure to install and require the whatwg-fetch polyfill from npm.

npm install --save whatwg-fetch

Image Note  The GitHub API is limited to 60 requests per hour for unregistered users. To learn more about GitHub API, visit https://developer.github.com/v3/.

If you test the application, you will see a list of repositories when navigating to the Repo component. Next, you will create a new route where you can show specific repository details. The idea is to get the URL to look something like /repos/details/repo_name.

You need to create a new RepoDetails component and update the routes in the App.js file, but before you do all that, let’s edit the Repos component to add links to repositories list and to load the RepoDetails as a nested child. The updated code is shown in Listing 5-12 (code parts that didn’t change were omitted for brevity).

In the sequence, let’s create the RepoDetails component. There are two things to notice in this code:

  • The React Router will inject the repo_name parameter in the component’s properties. You can use this value to fetch the GitHub API and get the project’s details.
  • In all previous examples of this book, you always fetched data in the componentDidMount lifecycle method. In the RepoDetails case, you need to implement the fetch in an additional lifecycle method: componentWillReceiveProps. This is necessary because the component may keep receiving new parameters as the user clicks in different repositories after it is mounted. In this case, the componentDidMount won’t be called again. Instead, componentWillReceiveProps will be invoked.

Listing 5-13 shows the complete code for RepoDetails component.

To finish your implementation of the nested Repo details route, you need to update the main App.js file. You import the new component and update the Router component implementing the details route as a child of the repos route, declaring the named parameter of repo_name, as shown in Listing 5-14.

When declaring a dynamic segment inside a route (such as :repo_name), React Router will inject any data that is in that part of the URL into a parameter attribute inside the component props.

If you followed along, you should be seeing something like Figure 5-5.

9781484212615_Fig05-05.jpg

Figure 5-5. Nested routes inside the Repos component

Setting Active Links

The Link component has nice additional touch: it accepts an optional prop called activeClassName. If this prop is set, it will automatically add this class name to active links. Let’s add this prop to your App component, as shown in Listing 5-15.

Passing Props

There’s a big problem with your implementation so far: you doing an unnecessary fetch. The GitHub API already provides all the repositories details when you first fetch https://api.github.com/users/pro-react/repos. You could pass all the data about the repositories down as props to render on the repoDetails component. There are two ways of passing props in React Router: by specifying props on the route configuration object or by injecting props on a clone of the children. The first one is more idiomatic, but it won’t solve all problems. The further looks a little “hacky” but does allow for more flexibility.

Props on the Route Configuration

The <Route> component is just a declarative way to configure a route; it is not rendered as a regular React component. When active, it renders the specified component instead. The <Route path="about" component={About} /> element, for example, renders the About component when the route is active. What’s interesting to notice is that besides rendering the specified component, the React Router injects all the Route properties inside the component’s props, which means that any additional props you define in the route will be accessible by the component.

To illustrate, let’s make the About component receive its title as props from the route. First, you add an arbitrary “title” prop to the about route:

React.render((
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Home}/>
      <Route path="about" component={About} title="About Us" />
      <Route path="repos" component={Repos}>
        <Route path="details/:repo_name" component={RepoDetails} />
      </Route>
    </Route>
  </Router>
), document.getElementById(’root’));

Next, in the About component, you access the route configuration from this.props.route:

import React, { Component } from ’react’;

class About extends Component {
  render() {
    return (
      <h1>{this.props.route.title}</h1>
    );
  }
}

export default About;

Cloning and Injecting Props on Children

Another approach, especially useful for dynamic props, is to clone the child component that gets injected as props by React Router, which gives you the opportunity to pass additional props in the process.

That’s exactly the case for the GitHub Repos project you’ve been building. You want to pass the repositories’ data you fetched in the Repo component to the RepoDetails component, but as you already know, the React Router will automatically create the RepoDetails component and inject it into the Repo’s props.children, which doesn’t give you any chance to manipulate its props.

Inside the Repo component, instead of simply rendering this.props.children provided by the router, you clone it and inject additional props (the list of repositories), as shown in Listing 5-16.

Now the RepoDetails will be treated as a pure component. It won’t have internal state, it’ll just receive and display props. You remove the constructor, componentWillReceiveProps, componentDidMount, and fetchData methods, and change the render method to find the repository based on the URL parameter. Listing 5-17 shows the updated RepoDetails.js.

Image Note  As mentioned, Array.prototype.find is a new method not supported on older browsers. Make sure to install the polyfills from Babel with npm install --save babel-polyfill and import it on the JavaScript module with import ’babel-polyfill’.

Decoupling the UI from the URL

While the details route works pretty well, you ended up with a URL segment that is a bit too long: /repos/details/:repo_name. It would be nice if you could change the URL segment to the smaller and singular form of /repo/:repo_name, but still render the RepoDetails component nested inside App Image Repos. In React Router, it is possible to do this kind of setup using an absolute path in the route definition.

So, instead of

render((
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Home}/>
      <Route path="about" component={About} />
      <Route path="repos" component={Repos}>
        <Route path="details/:repo_name" component={RepoDetails} />
      </Route>
    </Route>
  </Router>
), document.getElementById(’root’));

you use an absolute path, like

render((
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Home}/>
      <Route path="about" component={About} />
      <Route path="repos" component={Repos}>
        <Route path="/repo/:repo_name" component={RepoDetails} />
      </Route>
    </Route>
  </Router>
), document.getElementById(’root’));

Of course, you also need to update the links on the Repo component to reflect the new URL, as show in Listing 5-18.

The new URLs are shown in Figure 5-6.

9781484212615_Fig05-06.jpg

Figure 5-6. The UI keeps nested hierarchy, but with custom decoupled routes

Changing Routes Programmatically

The Link component you used earlier provides a nice way for the end user to transition between routes, but sometimes you need to be able to do it programmatically from inside your components. You might want to automatically go back or redirect the user to a different route under certain circumstances.

For this purpose, React Router automatically injects its history object into all components that it mounts. The history object is responsible for managing the browser’s history stack, and provides the methods for navigation shown in Table 5-1.

Table 5-1. History Methods

Method

Description

pushState

The basic history navigation method transitions to a new URL. You can optionally pass a parameters object. Example:

history.pushState(null, ’/users/123’)

history.pushState({showGrades: true}, ’/users/123’)

replaceState

Has the same syntax as pushState, but it replaces the current URL with a new one. It’s analogous to a redirect, because it replaces the URL without affecting the length of the history.

goBack

Go back one entry in the navigation history.

goForward

Go forward one entry in the navigation history.

Go

Go forward or backward in the history by n or -n

createHref

Makes a URL, using the router’s config.

To illustrate, let’s create a new Server Error route. From the Repos component you redirect to this new route if the fetch method can’t connect to the API. Listings 5-19 and 5-20 show the new ServerError component and the updated routes in the App.js, respectively.

Back to the Repos component, you use the pushState method inside the fetch’s catch statement, as shown in Listing 5-21.

When trying to access the /repos route after the GitHub API limit has expired, the component changes the route to the error route, as shown in Figure 5-7.

9781484212615_Fig05-07.jpg

Figure 5-7. Route redirection from inside the component

Of course, you don’t have to exceed the GitHub’s API rate limit to test this. Simply disconnect the Internet and try accessing the repos route. The fetch will retry three times and fail.

Histories

React Router is built on top of the History library (remember that when you installed the React Router using npm, you also installed History). Its purpose is to abstract URL and session management, providing a common API for manipulating the history stack and the URL across different browsers, testing environments, and platforms.

The History library has different possible setups. By default, React Router uses the hash history setup, which uses the hash (#) portion of the URL creating routes that look like example.com/#/path.

The hash history is the default setup because it works on older browsers (Internet Explorer 8 and 9) and doesn’t require any server configuration. If your application doesn’t need to run on legacy browsers and you have the possibility to configure your server, the ideal approach is to use the browser history setup, which creates real URLs that look like example.com/path.

Image Note  Server Configuration: The browser history setup can generate real looking URLs without reloading the page. But what happens if the user refreshes or bookmarks on a deep nested URL? These URLs are dynamically generated at the browser; they do not correspond to real paths on the server, and since any URL will always hit the server on the first request, it will likely return a Page Not Found error.

To work with browser history setup, you need to make rewrite configurations on your server, so when the user hits /some-path on the browser, the server will serve index page from where React Router will render the right view.

The Webpack dev server has the historyApiFallback option to always render the index page for unknown paths (and if you are using this book’s boilerplate app, this configuration is already in place). Node.js and all common web servers such as Apache and Nginx have such configurations. Please refer to the React Router’s documentation and your server’s documentation.

To implement the browser history setup, you need to import the createBrowserHistory method from the History library. You can then invoke it, passing the generated browser history configuration as the history prop of the Router component. Let’s implement it in your sample application, as shown in Listing 5-22.

Kanban App: Routing

So far, the Kanban application has been very effective as an exercise, but it’s not yet very useful on its own because you can’t edit or create new cards. Let’s implement these two features using routes. The /new route will show a form to create a new card, whereas the /edit/:card_id route will show a form with the card’s current properties so the user can edit it. Both NewCard and EditCard will be new components, and since both share much of the same characteristics (like the complete card form), you will also create a CardForm component that will be used by both and retain all the shared UI.

Since you’re going to implement code in lots of different files, let’s review everything that needs to be done:

  • Starting bottom up, let’s create the CardForm component.
  • Next, you create the NewCard and EditCard components.
  • In the sequence, you edit the App.js to set up the new routes.
  • In the KanbanBoardContainer class, you create methods for creating and editing cards. You pass these methods as props to the NewCard and EditCard components.

Before you start, make sure to install React Router and History: npm install --save [email protected] [email protected].

CardForm Component

As mentioned, since the NewCard and EditCard components share much of the same UI, you will create the CardForm component and use it in both components. It will contain a form (that needs to be flexible enough to be used blank or pre-filled with existing card values.) and an overlay. The form will appear as a modal on top of the Kanban board, and the overlay will be used behind the modal to “darken” everything behind it. Figure 5-8 shows the desired effect.

9781484212615_Fig05-08.jpg

Figure 5-8. Form and overlay

The CardForm component will be a pure component. It won’t have state. Both NewCard or EditCard components need to provide the following props to the CardForm:

  • An object (let’s call it a draft card) containing the card values to display on the form. In the case of EditCard, this object will contain the values of the card being edited. In the case of NewCard, this object will contain blank/default values for a new card.
  • The label of the Submit button.
  • Functions to handle form field change and form submit.
  • A function to handle the modal closing (when the user clicks outside the modal).

Listing 5-23 shows the CardForm component source code.

Next, in order to create the results shown in Figure 5-8, you need to add some additional styling for the form and overlay. Listing 5-24 shows the additional styles.

NewCard and EditCard Components

Let’s move on to the NewCard end EditCard components. They have a lot in common: both will hold the draft card state, render the CardForm, and provide it with callbacks to manipulate that state and persist the new/edited card.

Starting with the NewCard component, Listing 5-25 shows its source code.

There are a few things to notice in the code above:

  • When the component mounts, it sets the component state to an empty draft card with some default values and a temporary ID (based on the current date). The values of this draft card will be presented in the CardForm, and for every change you update the state.
  • When the user submits the form, you save the new card by invoking the cardCallbacks.addCard that came down as props from the KanbanBoardContainer.

That’s all for the NewCard component. Let’s move to the EditCard, which will be similar except for the fact that the EditCard expects to receive a card_id querystring parameter from the route. With the card id you can filter the card information and set the draft card in its state with the values from the card the user wants to edit, as shown in Listing 5-26.

Setting Up the Routes

Let’s skip the KanbanBoardContainer for a minute to set up the routes on the App.js file. This will help you better comprehend how you will modify the KanbanBoardContainer later. You will create three routes: an index route that leads to the Kanban board, a new route associated with the NewCard component, and an edit/:card_id route associated with the EditCard component. It’s also important to note that both the new and the edit routes will be nested inside the Kanban. That’s so you can see the Kanban board and the cards behind the form; you don’t want the form to be a completely isolated section. Listing 5-27 shows the App.js with the routing.

Creating the Callbacks and Rendering the Children on KanbanBoardContainer

The first thing you do in the KanbanBoardContainer component is create the two methods for creating and updating cards. They will be very similar to the analogous methods for tasks. Listing 5-28 shows the addCard method and Listing 5-29 shows the updateCard.

Finally, you need to update the render method. You don’t get to render the KanbanBoard manually; it will be injected by the router. The problem is that there’s no way to add new props to a component that the router passes as children, but the alternative is actually pretty simple. As you saw earlier, you will clone the props.children injected by the router and add the new props, as shown in Listing 5-30.

Rendering the Card Forms in the KanbanBoard

In the routes you configured earlier in the App.js file, the NewCard and EditCard components are children of KanbanBoard. When the user points to the routes /new or /edit, the router will inject the corresponding component as a children prop in the KanbanBoard. You need now to edit the KanbanBoard to clone the children props (to insert new props such as the list of cards and card callbacks) and render it. It is pretty much the same thing you just did for the KanbanBoardContainer. Listing 5-31 shows the new render function.

Finishing Touches: Transitioning

Your application is all wired up and is already working. If you manually input the /new route or the /edit/:card_id (with a valid card id, of course), everything will work as expected, but that’s not very practical, so let’s finish by making handling transitions to the routes, starting with the new route.

You will create a new link (using the Link component) inside the KanbanBoard component, pointing to new. See Listing 5-32.

You also add some styling on the link. It will be a round button absolutely positioned on the bottom right side of the screen, as shown in Listing 5-33 and Figure 5-9.

9781484212615_Fig05-09.jpg

Figure 5-9. The add card button

Finally, let’s also add a Link component to the card. Similar to the New button, you will style it so it doesn’t appear as a plain link. In the case of the card, you even use some CSS tricks to only show the edit link when the user is hovering over the card. Listing 5-34 shows the code for the Card component, Listing 5-35 shows the additional styling for this element, and Figure 5-10 shows the final result.

9781484212615_Fig05-10.jpg

Figure 5-10. The Edit button on the Card component

Summary

This chapter discussed routing. You started by manually implementing a basic routing and understanding the complexities that can arise with nested routing. Next, you learned how to use one of the most used libraries in the React community, the React Router. You saw how to set up nested routes and a default home route, how to pass parameters through the route to a component, and how to directly pass properties from a component to its children. You then learned how make transitions via code using the History object.

Your applications (specially the Kanban app) have become larger and more feature rich since their first iterations and are starting to feel growing pains. In the next chapter, you’re going to study Flux, an application architecture that complements React and will help you better organize your projects.

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

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