TIL/2021–02–13 & 14
Day 65 and 66: Treehouse Full Stack JavaScript Techdegree
React Router 4 Basics Continuation
Displaying 404 Error Routes using Switch
Currently in our directory app, once we type a URL that does not match any of the route paths, nothing renders
We should give feedback when something goes wrong - like a user visits the wrong url or mistypes the link
Web developers usually create a 404 error page that displays when the webpage the user is trying to reach cannot be found on the server
React router lets us create a 404 like error route that displays when a URL’s path does not match any of the paths defined in our routes
In our components directory we’ll see a file called NotFound.js that contains a stateless functional component that renders and error icon and text that says page not found
First let’s import the file in App.js
import NotFound from "./NotFound";
And we will create a new <Route />
And unlike the other routes we will not specify the path but just the component that will be rendered
<Route component={NotFound} />
Though a route without the path will always render the component
So to render the NotFound component only if the URL does not match any Route, we will use React Router’s Switch Component
So first let us import Switch from react-router-dom
import { BrowserRouter, Route, Switch } from "react-router-dom";
Then wrap all our Routes inside the Switch component
Switch will only render the first Route that matches the URL
For example, we placed a /teachers path on the URL and then switch will look for the first Route that has the /teachers path. And it will execute or render that Route.
If switch does not find any matching Route, it is going to fall back to the catch all route - which is our Route that renders the NotFound component
const App = () => (
<BrowserRouter>
<div className="container">
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" render={() => <About />} />
<Route path="/teachers" component={Teachers} />
<Route path="/courses" component={Courses} />
<Route component={NotFound} />
</Switch>
</div>
</BrowserRouter>
);
And it works!
------
Passing Data to components via routes
Let’s refactor our Courses since they all display the same type of data
On HTML.js
import React from 'react';
import Course from './Course';
import { HTMLCourses } from '../../data/courses';
const HTML = (props) => {
let courses = HTMLCourses.map((course) => {
return <Course title={course.title}
desc={course.description}
img={course.img_src}
key={course.id} />
});
return (
<div>
<ul>
{courses}
</ul>
</div>
);
}
export default HTML;
Now turned CourseContainer
import React from "react";
import Course from "./Course";
const CourseContainer = (props) => {
let courses = props.data.map((course) => {
return (
<Course
title={course.title}
desc={course.description}
img={course.img_src}
key={course.id}
/>
);
});
return (
<div>
<ul>{courses}</ul>
</div>
);
};
export default CourseContainer;
Then in Courses.js
import React from "react";
import { NavLink, Route, Redirect } from "react-router-dom";
import { HTMLCourses, CSSCourses, JSCourses } from "../data/courses";
//App Components
import CourseContainer from "./courses/CourseContainer";
const Courses = ({ match }) => (
<div className="main-content courses">
<div className="course-header group">
<h2>Courses</h2>
<ul className="course-nav">
<li>
<NavLink to={`${match.url}/html`}>HTML</NavLink>
</li>
<li>
<NavLink to={`${match.url}/css`}>CSS</NavLink>
</li>
<li>
<NavLink to={`${match.url}/javascript`}>JavaScript</NavLink>
</li>
</ul>
</div>
{/* Write routes here... */}
<Route
exact
path={match.path}
render={() => <Redirect to={`${match.path}/html`} />}
/>
<Route
path={`${match.path}/html`}
render={() => <CourseContainer data={HTMLCourses} />}
/>
<Route
path={`${match.path}/css`}
render={() => <CourseContainer data={CSSCourses} />}
/>
<Route
path={`${match.path}/javascript`}
render={() => <CourseContainer data={JSCourses} />}
/>
</div>
);
export default Courses;
We changed our Route component prop to a render prop so we can pass data to the CourseContainer tag component
--------
Using URL parameters
With React Router we are able to declare dynamic routes using special URL parameters - the data passed in your URL parameters can be displayed on the app.
We can use URL parameters to create one route that matches a portion of a path dynamically like a teacher’s name and topic area.
Then we can extract that data from the URL - and render it inside the component
In our Components folder we have a Component called Featured.js and that is what renders the featured teacher page
First on App.js let’s import Featured
import Featured from "./Featured"
const App = () => (
<BrowserRouter>
<div className="container">
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" render={() => <About />} />
<Route exact path="/teachers" component={Teachers} />
<Route path="/teachers" component={Featured} />
<Route path="/courses" component={Courses} />
<Route component={NotFound} />
</Switch>
</div>
</BrowserRouter>
);
So now the featured component will render when you include a subpath in the teachers URL
So to display content in our app from the data passed to the URL, we use special URL parameters
We define a parameter in a Route with a colon :
<Route path="/teachers/:name" component={Featured} />
Note: we can give the parameter any name we want as long as we include a colon in front of it
The parameters of the path are dynamic and made available to components via props - this means we are able to use what’s being passed as data in our component
We can access it through that match object - params property
So, on our featured.js we can call that match object by placing it in our Featured parameter as an object and get the name by: match.params.name
import React from "react";
const Featured = ({ match }) => {
const name = match.params.name;
return (
<div className="main-content">
<h2>{name} </h2>
<p>
Introducing <strong>{name}</strong>, a teacher who loves teaching
courses about <strong></strong>!
</p>
</div>
);
};
export default Featured;
And it works
Let’s add one more parameter - this time we’ll display the teacher’s topic area
<Route path="/teachers/:topic/:name" component={Featured} />
import React from "react";
const Featured = ({ match }) => {
const name = match.params.name;
const topic = match.params.topic;
return (
<div className="main-content">
<h2>{name} </h2>
<p>
Introducing <strong>{name}</strong>, a teacher who loves teaching
courses about <strong>{topic}</strong>!
</p>
</div>
);
};
export default Featured;
The Featured component is using the params being passed from the URL to render both renzo and javascript
And we can also create links to several featured teachers using any topic and name we want
On Home.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
class Home extends Component {
render() {
return (
<div className="main-content home">
<h2>Front End Course Directory</h2>
<p>
This fun directory is a project for the <em>React Router Basics</em>{" "}
course on Treehouse.
</p>
<p>
Learn front end web development and much more! This simple directory
app offers a preview of our course library. Choose from many hours of
content, from HTML to CSS to JavaScript. Learn to code and get the
skills you need to launch a new career in front end web development.
</p>
<p>
We have thousands of videos created by expert teachers on web design
and front end development. Our library is continually refreshed with
the latest on web technology so you will never fall behind.
</p>
<hr />
<h3> Featured Teachers</h3>
<Link to="/teachers/JavaScript/Renzo">Renzo</Link>
</div>
);
}
}
export default Home;
And it works!
We can also separate URL parameters with a dash -
For example, to display a teacher’s first and last name
On App.js
<Route path="/teachers/:topic/:fname-:lname" component={Featured} />
On Featured.js
import React from "react";
const Featured = ({ match }) => {
const name = `${match.params.first} ${match.params.last}`;
const topic = match.params.topic;
return (
<div className="main-content">
<h2>{name} </h2>
<p>
Introducing <strong>{name}</strong>, a teacher who loves teaching
courses about <strong>{topic}</strong>!
</p>
</div>
);
};
export default Featured;
And it works!
To make a parameter optional, just add a ? at the end of the parameter name
<Route path="teachers/:topic/:fname/:lname?" component={Featured} />
For example, I will make the last name optional
<Route path="/teachers/:topic/:fname/:lname?" component={Featured} />
import React from "react";
const Featured = ({ match }) => {
const name = match.params.lname
? `${match.params.fname} ${match.params.lname}`
: match.params.fname;
const topic = match.params.topic;
return (
<div className="main-content">
<h2>{name} </h2>
<p>
Introducing <strong>{name}</strong>, a teacher who loves teaching
courses about <strong>{topic}</strong>!
</p>
</div>
);
};
export default Featured;
Then if there is a last name
------
Navigating Routes Programmatically
Most of the navigation in our app will be done using Link or the NavLink component - though there will be times when we’ll need to programmatically change the path in the url - a common example is changing the path in response of a form submission
So on our Home Component we will add a form - the idea is that when we place the information on the form and submit we will be redirected using the data submitted from the form
import React, { Component } from "react";
class Home extends Component {
render() {
return (
<div className="main-content home">
<h2>Front End Course Directory</h2>
<p>
This fun directory is a project for the <em>React Router Basics</em>{" "}
course on Treehouse.
</p>
<p>
Learn front end web development and much more! This simple directory
app offers a preview of our course library. Choose from many hours of
content, from HTML to CSS to JavaScript. Learn to code and get the
skills you need to launch a new career in front end web development.
</p>
<p>
We have thousands of videos created by expert teachers on web design
and front end development. Our library is continually refreshed with
the latest on web technology so you will never fall behind.
</p>
<hr />
<h3> Featured Teachers</h3>
<form>
<input type="text" placeholder="Name" />
<input type="text" placeholder="Topic" />
<button type="submit">Go!</button>
</form>
</div>
);
}
}
export default Home;
We can use refs to reference and access the input value
When used on an HTML element the ref attribute takes a callback function that receives the underlying DOM element as its argument, and in both cases it is the input - these two callbacks are executed immediately after the home component is mounted to the DOM - so when the inputs are rendered to the page, they return a reference which we can access with this.name and this.topic. And we can access their values with .value
<input
type="text"
placeholder="Name"
ref={(input) => (this.name = input)}
/>
<input
type="text"
placeholder="Topic"
ref={(input) => (this.topic = input)}
/>
React router passes rendered components information about the current path and URL the route is matching. The component also gets passed a history object that listens for changes in the current URL, keeps track of browser history and the number of entries in the history stack
History stack is the previously visited URLs - every time a user navigates to a new path, that URL is stored in the history stack