<info 340/>

React Router

and other libraries

 

 

 

Tim Carlson

Plan for Today

  • React Libraries (example: ReactStrap)

  • React Router 

React Libraries

React components are structured to be self-contained, re-usable elements... so there are lots  of pre-defined components online you can use!

In order to use a component in your app:

  1. Find it! (via npm, google, etc). Read the documentation
  2. Install it! npm install lib-name
  3. Import it! import { ComponentName } from 'lib-name'
    • (import structure may vary per library)
  4. Render it! <ComponentName />
    • Remember to pass any expected props!

Example using ReactStrap

Replace the bootstrap jscript (jQuery based) library
Also add the react-router-dom library.

// within package.json

{  "name": "react-router",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "bootstrap": "^4.1.3",
    "react": "^16.6.0",
    "react-dom": "^16.6.0",
    "react-router-dom": "^4.3.1",
    "react-scripts": "2.0.5",
    "reactstrap": "^6.5.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ] }
// within App.js

import React, { Component } from 'react';

import { Collapse, Button, CardBody, Card } from 'reactstrap';

import {BrowserRouter, Route, Switch, Link, NavLink} from 'react-router-dom';

>npm install reactstrap

>npm install react-router-dom

Demo- Blog Page

Component Lifecycle

The Component class has methods that are called at each stage of the component's "Lifecycle". Override these methods to perform an action at that point.

class MyComponent extends Component {
  constructor(props){
    super(props)
    //called at Compnent instantiation (not on screen yet)
    //initialize state here
  }

  componentDidMount() {
    //called when put on screen (visible, after render())
    //do AJAX requests here!
  }
  
  componentDidUpdate(revProps, prevState, snapshot) {
    //called when changing. Less common
  }

  componentWillUnmount() {
    //called when removed from screen
    //do asynchronous cleanup here
  }
}

Loading Data

To use fetch() in React (which is compiled via webpack & Node), you will need to install and import the polyfill.

# Install polyfill (on command line)
npm install --save whatwg-fetch
//import polyfill (in JavaScript)
import 'whatwg-fetch'; //loading "globally"

AJAX & React

Send an AJAX request from the componentDidMount() method; the .then() callback should update the state when new data arrives!

class MyComponent extends Componet {
  constructor(props){
    super(props);
    this.state = {data: []}; //initialize as empty!
  }

  componentDidMount() {    
    fetch(dataUri) //send AJAX request
      .then((res) => res.json())
      .then((data) => {
        let processedData = data.filter().map() //process the data
        this.setState({data: processedData}) //change the state!
      })
  }

  render() {
    //convert data into DOM
    //Note that this works even before data is loaded (when empty!)
    let dataItems = this.state.data.map((item) => {
      return <li>{item.value}</li>; //get item from data
    })
    return (<ul>{dataItems}</ul>); //render the list
  }
}

React Router

Single-Page Applications

Client-Side Routing

Render a different component depending on the URL.

"IF the current url MATCHES a route, render this Component!"

class App extends Component {
  render() {
    //pick a component based on the URL
    let componentToRender = null;
    if(currentUrl === 'domain/home'){ //pseudocode comparison with URL
      componentToRender = <HomePage />;
    }
    else if(currentUrl === 'domain/about'){
      componentToRender = <AboutPage />;
    }
    //render that component
    return <div>{componentToRender}</div>;
  }
}

react-router

A library of React Components that can determine which other components to render based on the current URL.

https://reactrouter.com 

Install Library

Import Components

//in index.js
import {BrowserRouter, Route} from 'react-router-dom';
# Install library (on command line)
npm install react-router-dom

React Router Demo

Original Code

App component generates the nav with Home, About and Blog

And then creates a NewPost Page, AboutPage and the BlogPostLists

// from app.js, there is code above this too 
// 
let postLinks = Object.keys(posts).map((date) => {
    return (
      <li key={date}>
        <a href={'/blog/posts/' + date} className="nav-link">{date}</a>
      </li>
    )
  });

  return (
    <div className="container">
      <h1>My Blog</h1>
      <nav>
        <ul className="nav">
          <li>
            <a href='/' className="nav-link">Home</a>
          </li>
          <li>
            <a href='/about' className="nav-link">About</a>
          </li>
          <li>
            <a href='/blog' className="nav-link">Blog</a>
          </li>
          {postLinks}
        </ul>
      </nav>
      <NewPostPage postCallback={addPost} />
      <AboutPage />
      <BlogPostList posts={posts} />
    </div>
  );
}
//there is code below this too!

BrowserRouter 

// App.js
import React, { useState } from 'react';

import { BrowserRouter, Route } from 'react-router-dom'; // two base classes we need to make router work

function App(props) {

  //... more code
   

  return (
	<BrowserRouter>
		<div className="container">
			<h1>My Blog</h1>
		<nav>
// ... more code 
		</nav>
		<NewPostPage postCallback={addPost} />
		<AboutPage />
		<BlogPostList posts={posts} />
		</div>
	</BrowserRouter>
	);
}

Wrap all the app content within BrowserRouter

Import BrowserRouter and Route components

<BrowserRouter>

If you want a component to be route aware and adjusted based on route, it must be a child of the <BrowserRouter> component.

Thus we place <BrowserRouter>  </BrowserRouter> around all our components in the example.

class App extends Component {
  render() {
    return (
      <BrowserRouter>
        {/* if currentUrl == '/home', render <HomePage> */}
        <Route path='/home' component={HomePage} />

        {/* if currentUrl == '/about', render <AboutPage> */}
        <Route path='/about' component={AboutPage} />
      </BrowserRouter>
    );
  }
}

<Route>

Route-based views are handled using the <route> component

This component will render some content ONLY when the URL matches the specified path.

Both the path to match and the component to render are passed in as props

function App(props)  {

  <BrowserRouter>
 //... more code
        <Route path="/post"> 
          <NewPostPage postCallback={addPost} />
        </Route>
        <Route path="/about">
          <AboutPage />
        </Route>
        <Route path="/blog">
          <BlogPostList posts={posts} />
        </Route>
      </div>
    </BrowserRouter>

To enforce mutual exclusivity use "switch"

Each <Route> determines whether the component should be rendered.

They are not mutually exclusive from each other

You can force mutual exclusivity by nesting inside of a switch statement

<BrowserRouter>
  <Switch>
    <Route path='/home' component={HomePage} />
    <Route path='/about' component={AboutPage} />
  </Switch>
</BrowserRouter>

The option is to use the "exact" prop

<BrowserRouter>
  
    <Route exact path='/home' component={HomePage} />
    <Route path='/about' component={AboutPage} />
  
</BrowserRouter>

Using Link or NavLink rather than <a> anchor

<a href='/' className="nav-link">Home</a>

Changed to:

<NavLink exact to='/' className="nav-link" activeClassName="myActiveLink">Home</NavLink>

The component will render as an <a> element with a special onClick handler that keeps the browser from loading a new page.

Thus you can specify an content that you would put in the <a> (such as the hyperlink text) as child content of the <NavLink>.

 

This keeps the page from re-rendering when just routing to another route within the application.

API URIs

A web service's URI has two parts:

  1. The Base URI

     

  2. An EndPoint

https://api.github.com

/users/{username}/repos

/repos/:owner/:repo

/search/repositories

/emojis

A variable (also :username)

Using a URL Parameter to render individual Blog

<BrowserRouter>
  
      <Route exact path="/"> 
        <WelcomePage />
      </Route>
      <Route path="/post"> 
        <NewPostPage postCallback={addPost} />
      </Route>
      <Route path="/about">
        <AboutPage />
      </Route>
      <Route exact path="/blog">
        <BlogPostList posts={posts} />
      </Route>
      <Route path="/blog/posts/:postDate">
        <BlogPost />
      </Route>

</BrowserRouter>

Using a URL Parameter to render individual Blog

function BlogPost(props) {

  const urlParams = useParams();
  console.log(urlParams)
  let date = props.postDate;
  let post = props.post;

  if (props.date === undefined) {

    date = urlParams.postDate;
    post = BLOG_POSTS[date]; //pretend database access 
    console.log("no Blog specified")
  };

  return (
    <div>
      <h2>Post on {date}</h2>
      <p>{post}</p>
    </div>
  );
}

If we didn't get a prop

Redirect if we have a route we don't know

<Switch>
  <Route exact path="/"> 
    <WelcomePage />
    </Route>
<Route path="/post"> {/* this is basically an if statement*/}
<NewPostPage postCallback={addPost} />
  </Route>
<Route path="/about">
  <AboutPage />
  </Route>
<Route exact path="/blog">
  <BlogPostList posts={posts} />
    </Route>
<Route path="/blog/posts/:postDate">
  <BlogPost />
  </Route>
<Route path="/">
  {/* match to everything */}
{/* <ErrorPage/> */}
<Redirect to="/" />
  </Route>
</Switch>

Match to any other route and redirect to homepage

Example : using URI variables for route to show

So if you pass something that has path of "/blog/posts/foo"

"foo" is stored in the postID variable

And then component "BlogPost" is rendered

      <Route path="/blog/posts/:postId" component={BlogPost} />

To render "BlogPost" we need to have some props!

Example : Using that variable from the URL

We don't have any props passed with the component

      <Route path="/blog/posts/:postId" component={BlogPost} />

We can get to the postId variable through "this.props.match.params.postId"

But we have the variable from the URI

Example : Using that variable from the URL

      <Route path="/blog/posts/:postId" component={BlogPost} />

We can get to the postId variable through "this.props.match.params.postId"

class BlogPost extends Component {
  constructor(props){
    super(props);
    this.state = {post: ""}
  }

  componentDidMount() {
    if(this.props.match && this.props.match.params.postId) {
      //fetch it from server
      this.setState({post: BLOG_POSTS[this.props.match.params.postId]})
    }
  }

  render() {

    console.log(this.props)

    let date = this.props.date || this.props.match.params.postId;    
    let post = this.props.post || BLOG_POSTS[this.props.match.params.postId]; //this.state.post  

    return (
      <div>
        <h2>Post on {date}</h2>
        <p>{post}</p>
      </div>
    );
  }
}

Using "NavLink" rather than Link anchor

      <a href={'/blog/posts/'+date} className="nav-link">{date}</a>

Changed to:

       <NavLink to={'/blog/posts/'+date} className="nav-link">{date}</Link>

The component will render as an <a> element with a special onClick handler that keeps the browser from loading a new page.

 

Thus you can specify an content that you would put in the <a> (such as the hyperlink text) as child content of the <Link>.

 

This keeps the page from re-rendering when just routing to another route within the application.

Example - Collapse()

//track state of collapse

class App extends Component {
  constructor(props){
    super(props);
    this.state = {posts: [], collapse:false} //added the collapse state property for tracking collapse
  }

// add this code as a public function for toggle so we don't lose the 'this' context

/* toggle callback for collapse() below */
  toggle = () => {
    this.setState((currentState) => {
      return { collapse: !currentState.collapse }
    });
  }

// add this code example to the app.js in the render() method

     <div className="container">
        <h1>My Blog</h1>

          {/* code from reactstrap documentation */}
          <Button color="primary" onClick={this.toggle} style={{ marginBottom: '1rem' }}>Toggle</Button>
          <Collapse isOpen={this.state.collapse}>
          <Card>
            <CardBody>
            Anim pariatur cliche reprehenderit,
             enim eiusmod high life accusamus terry richardson ad squid. Nihil
             anim keffiyeh helvetica, craft beer labore wes anderson cred
             nesciunt sapiente ea proident.
            </CardBody>
          </Card>
        </Collapse> 


info340sp21-react-router

By Tim Carlson

info340sp21-react-router

  • 8