Fullstack React - Chapter 9 Notes

Summary

This post contains notes from chapter 9 in Fullstack React - "Routing". The purpose of this chapter is to gain a better understanding of routing in React applications.

Overview 

In this chapter, we learn how to build an application made up of different "pages". React applications are typically Single Page Applications (SPA). In order to support different urls in our applications, we can leverage React Router to "mimic" this behavior. 

This chapter also touches on login/logout. I don't know if I would use their solution (or parts of it) in a real world scenario and I will probably do some research on how to implement login / logout at a later time. Their solution did have what appears to be useful steps on how to handle the login / logout, authorizing of certain routes, and redirecting to a route after they login.

React Router v4

The book focused on the alpha version of React Router v4. I tried creating a practice application with create-react-app, following the same steps in the book, but I couldn't get it to work with the latest version of React (4.1.1). I ended up looking at the react-router documentation and the tutorial video and I changed the examples from the book to use the components that the tutorials recommended for 4.1.1. The biggest changes that I noticed were:

  • We should use Route instead of Match
  • We should import react-router-dom instead of react-router
    • react-router-dom - use this version if you are building a react web application
    • react-router-native - use this if you are building a react native application
  • We needed a BrowserRouter component that wraps our routes.
  • The BrowserRouter component requires that it only has 1 child, so we need to wrap our routes in a <div> or a <Switch>
    • If you wrap it with a div, it will render all routes that match the path
    • If you wrap it with a Switch, it will render the first component that matches the path.
Setting up react-router
First we need to add react-router-dom to our application using npm (we need to do this with a create-react-app as well).

Navigate to the project directory in a Terminal window and run the following command:

  • $ npm install react-router-dom --save


After we have react-router-dom installed, there are some best practices to follow to setup our application to use react-router. 

  • read the tutorial to fill this in. I should mention things like wrapping the application in a Router component and how history / hashHistory works.


Route component
The Route component determines the correct component to render based on the path in the browser's address bar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const App = () => (
  <Router>
    <div>
      <Route path="/person1" component={Person1} />
      <Route path="/person2" component={Person2} />
    </div>
  </Router>
);

// This component is rendered if we visit http://localhost:3000/person1
class Person1 extends React.Component {
  render () {
    return (
      <div>
        Person One
      </div>
    )
  }
}

// This component is rendered if we visit http://localhost:3000/person2
class Person2 extends React.Component {
  render () {
    return (
      <div>
        Person Two
      </div>
    )
  }
}

export default App;


Route exact
The way the Route component works is that it will render any components that match what is passed in using pathname.match(pattern). So if the application is currently loading the resource '/childpage', then that means the component that matches on '/' will be rendered and the component that matches on '/childpage' will be rendered. This isn't always desirable, so in order to get around this, we can tell the router to perform an exact match.

The following code example illustrates how the exact attribute works in code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const App = () => (
  <Router>
    <div>
      {/* Because we have exact on this Route component, it will only be rendered when we visit / */}
      <Route exact path="/" component={Root} />
      <Route path="/person1" component={Person1} />
      
    </div>
  </Router>
);

// This will only be rendered if we visit http://localhost:3000 or http://localhost:3000/
class Root extends React.Component {
  render () {
    return (
      <h1>Hello</h1>
    )
  }
}

// This component is rendered if we visit http://localhost:3000/person1
class Person1 extends React.Component {
  render () {
    return (
      <div>
        Person One
      </div>
    )
  }
}

export default App;



Switch
There are times where we want to render multiple components based on the path (i.e. header, breadcrumbs, navigation, etc.). In that scenario, we want to wrap our Routes in a <div> tag so they can all be rendered if they match the pattern.

However, there are other times where we only want to render the first Route that matches the pattern. For that we can use the Switch component. It will render the first and only the first Route that matches the path. If you use the Switch statement, you will likely want to use the exact property on some Routes so they don't prevent the route you intended to render from being rendered. In the following code block, we use the exact attribute on the root "/" path so the "/person1" path can still be rendered when a user visits "/person1".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const App = () => (
  <Router>
    <Switch>
      {/* If we didn't have the exact attribute on this Route, 
      then the /person1 Route would never be rendered even if the user visits /person1 */}
      <Route exact path="/" component={Root} />
      <Route path="/person1" component={Person1} />
    </Switch>
  </Router>
);

// This will only be rendered if we visit http://localhost:3000 or http://localhost:3000/
class Root extends React.Component {
  render () {
    return (
      <h1>Hello</h1>
    )
  }
}

// This component is rendered if we visit http://localhost:3000/person1
class Person1 extends React.Component {
  render () {
    return (
      <div>
        Person One
      </div>
    )
  }
}

export default App;

Setting up a Route for Dynamic Parameters
In order to match a dynamic url (a url with a querystring parameter), we can use the colon (:) syntax to indicate that this part of the url is dynamic. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const App = () => (
  <Router>
    <div>
      <Route path="/people/:personId" render={() => (
        <div>
          This is rendered when we visit http://localhost:3000/people/123
        </div>
      )} />
    </div>
  </Router>
);

export default App;

render vs component
The Route component provides two approaches for rendering components that match the path.

The component property lets you specify another component to render and we've already seen examples of this above.

The render property lets you specify a function that you can use to define the elements to render. Kind of like an inline function versus a separate standard function in C#.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const App = () => (
  <Router>
    <div>
      <Route exact path="/" component={Root} />
      <Route path="/person3" render={() => (
        <div>
          This is rendered when we visit http://localhost:3000/person3
        </div>
      )} />
    </div>
  </Router>
);

// This will only be rendered if we visit http://localhost:3000 or http://localhost:3000/
class Root extends React.Component {
  render () {
    return (
      <h1>Hello</h1>
    )
  }
}

export default App;


Route makes certain properties available to child components
  • pattern - (string) the portion of the pattern matched
  • pathname - (string) the portion of the pathname matched
  • isExact - (bool) whether or not the match is exact (v. partial)
  • location - the location matched
  • params - the values parse from the pathname corresponding by name to the dynamic segments of the pattern.

Link
Use the Link component instead of traditional anchor tags to link to other pages in my React application. By using the Link component, we are telling the browser to load that resource without causing a full post back to the server. By doing this, we avoid having to load the index.html and all root components on every request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

const Person1 = () => {
    return (
      <div>
        person 1
      </div>
    )
}

const Person2 = () => {
    return (
      <div>
        person 2
      </div>
    )
}

class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <ul>
            <li><Link to="/person1">Person 1</Link></li>
            <li><Link to="/person2">Person 2</Link></li>
          </ul>
          <Route path="/person1" component={Person1} />
          <Route path="/person2" component={Person2} />
        </div>
      </Router>      

    );
  }
}

export default App;

Links with Dynamic Parameters
We talked about how to create a Route to match a dynamic url, but to create a link to one in code, we use the ES6 template string syntax, which is delineated by a backtick `. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

const Person = (props) => {
    return (
      <div>
        Person Id: {props.match.params.personId}
      </div>
    )
}

class App extends Component {
  state = {
    personIds: [ 1, 2, 3 ]
  };

  render() {
    return (
      <Router>
        <div>
          {/* This is a link with a hard coded dynamic parameter */}
          <Link to="/people/1">Person 1</Link>
          <br />
          <ul>
            {
            this.state.personIds.map((personId) => (
              /* Notice how in code we use an ES6 template string signified by the backticks to set the to path */
              <li key={personId}><Link to={`/people/${personId}`}>Person {personId}</Link></li>
            ))
            }
          </ul>
          <Route path="/people/:personId" component={Person} />
        </div>
      </Router>      

    );
  }
}

export default App;

Best Practice for Link paths
Instead of putting in parent paths when constructing the to value in Link components, we can use the pathname property, which will make the components reusable across different parts of the application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

const Person = (props) => {
    return (
      <div>
        Person Id: {props.match.params.personId}
      </div>
    )
}

class App extends Component {
  state = {
    personIds: [ 1, 2, 3 ]
  };

  render() {
    return (
      <Router>
        <div>
          <ul>
            {
            this.state.personIds.map((personId) => (
              /* The /people portion is replaced with the dynamic pathname property */
              <li key={personId}><Link to={`${this.props.location.pathname}/${personId}`}>Person {personId}</Link></li>
            ))
            }
          </ul>
          <Route path="/people/:personId" component={Person} />
        </div>
      </Router>      

    );
  }
}

export default App;

Redirect
To perform an immediate redirect to a location when a component is rendered, we can use the Redirect component. In the following example, the page will load and redirect to /person1, which in turn will load the Person1 component. Once the Person1 component renders, it will redirect to /person, which in turn will load the Person2 component. The final result is that we will see Person 2 on the page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Redirect } from 'react-router-dom';

const Person1 = () => {
    return (
      <div>
        <Redirect to='/person2' />
      </div>
    )
}

const Person2 = () => {
    return (
      <div>
        Person 2
      </div>
    )
}

class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Redirect to='/person1' />
          <Route path="/person1" component={Person1} />
          <Route path="/person2" component={Person2} />
        </div>
      </Router>      

    );
  }
}

export default App;

Miss
The Miss component is kind of a catch all for when the user requests a resource that doesn't exist. If no Match exists for the requested resource, the Miss component will be rendered, which can be a place for us to tell the user the resource doesn't request or some other kind of message.

INSERT CODE SAMPLE HERE


ES6: Destructuring assignment syntax
The ES6 Destructuring assignment syntax is just a shorthand for letting you create variables with the same name as the properties that are on the object that is passed in to a method. In the following example, you can see how a variable called first and a variable called last are available in the getFullName method even though they weren't explicitly declared.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';

const samplePerson = { first: 'Steve', last: 'Martin ' };

class Person extends React.Component {
  getFullName = ({first, last}) => {
    return first + ' ' + last;
  }

  render () {
    return (
      <div>
          <br />
          Full Name: {this.getFullName(samplePerson)}
      </div>
    )
  }
}

export default Person;




Trick to highlight active links / menu items
We can leverage the properties that are passed down from the Match components and do a comparison to identify active links and highlight them accordingly, however, the Link component provides an easier way. We can pass Link a function and Link will invoke that function with 3 parameters (onClick, href, and isActive). We can use the isActive parameter to identify when to apply a different css class for active links. This may seem like a lot of code for a "hyperlink", but we could wrap the Link component with our own component that can contain this logic so we don't have to rewrite it all of the time.

Better way here: https://github.com/reactjs/react-router-tutorial/tree/master/lessons/05-active-links

  • onlyActiveOnIndex



INSERT CODE SAMPLE HERE



IndexRoute
https://github.com/reactjs/react-router-tutorial/tree/master/lessons/08-index-routes


browserHistory
https://github.com/reactjs/react-router-tutorial/tree/master/lessons/10-clean-urls

Navigating Programatically
https://github.com/reactjs/react-router-tutorial/tree/master/lessons/12-navigating

Login / Logout
This chapter introduces us to a couple of Login / Logout components and a pattern for handling authorization of certain components and on how to redirect users to the previous location after they logged in. I'm not going to include any code samples here, but I may revisit this when I implement logging in / out in my application.


No comments:

Post a Comment

} else { }