Tim Carlson
I'm a lecturer at University of Washington
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:
npm install lib-name
import { ComponentName } from 'lib-name'
<ComponentName />
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
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
}
}
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"
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
}
}
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.
//in index.js
import {BrowserRouter, Route} from 'react-router-dom';
# Install library (on command line)
npm install react-router-dom
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!
// 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
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-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>
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>
<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.
https://api.github.com
/users/{username}/repos
/repos/:owner/:repo
/search/repositories
/emojis
A variable (also
:username
)
<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>
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
<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
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!
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
<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>
);
}
}
<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.
//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>
By Tim Carlson