[React]: Basics

React

10/03/2019


Install Create React App

Docs on installing Create React App
BASH
npx create-react-app my-app
cd my-app

npx comes with npm(node package manager) version 5.2+

Under "scripts": in package.json, it has list of following commands

BASH
npm start

Start the application in my-app directory

BASH
npm build

Turn all source code from /src/ into code that browser understands(HTML/CSS) and put into /public/ directory.

BASH
npm run build

Creates a optimized build directory with source code. build folder is what we use to deploy the application.

BASH
npm test

Runs test code.

BASH
npm eject

Take out config files; not recommended since the source code is already optimized by professionals.

Barbel & webpack

Baebel makes sure that it will run across all browsers any versions by converting React JS files into older version of JS that web browser understands.

webpack is a module bundler that allows moduler code. It takes all imports and optimize them for production.

Class components

app.js

JSX
import React from "react"
import logo from "./logo.svg"
import "./App.css"
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>Hi I'm Ellis</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)
}
export default App

Above code block is just a function that returns a block of HTML

index.js

JS
ReactDOM.render(<App />, document.getElementById("root"))

In index.js, this code renders App function. Replaces id='root' from index.html to function App().

Creating class

Class has more functionalities than a function. To create a class, import the following. This enables JSX syntax.

JS
import React, { Component } from "react"

Otherwise, you can also do

JS
class App extends React.Component

Function to class

JSX
/* Function to class */
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>Hi I'm Ellis</p>
</header>
</div>
)
}
}
export default App

Accessing state

Class component allows access to state with constructor.

JSX
class App extends Component {
constructor() {
super()
this.state = {
string: "Ellis",
}
}
render() {
/* ... */
;<p> {this.state.string} </p>
/* ... */
}
}

super() calls constructor method in Component class which gives access to functions such as this.state, this.setState, etc.

Changing state

JS
<button onClick={() => this.setState({ string: "Min" })}> Change Text</button>
JS
{
;() => this.setState
} // JavaScript anonymous function that calls this.setState

As soon as the state changes, render() gets called again

app.js

JSX
class App extends Component {
constructor() {
super()
this.state = {
string: "Ellis",
}
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p> {this.state.string}</p>
<button onClick={() => this.setState({ string: "Min" })}>
{" "}
Change Text
</button>
</header>
</div>
)
}
}
export default App

JSX syntax

JS
<div className="">
<button onClick={ ... }>

JSX uses a slightly different attributes from HTML. It uses className, onClick to distinguish it from class="" and onclick. JSX mimics what HTML does so it can create a virtual DOM.

{} is used for JavaScript variables or expressions.

Dynamic content

To display a multiple users

JSX
constructor() {
super();
this.state = {
users: [
{
name: 'Bob',
id: '2'
},
{
name: 'Peter',
id: '3'
},
{
name: 'John',
id: '4'
}
]
};
}

To access each element

JSX
render() {
return (
<div className="App">
{ /* Iterate over every element*/
this.state.users.map(user =>
/* key is used to avoid rendering everything but particular obj*/
<h1 key={user.id}> {user.name}</h1>)
}
</div>
);
}

It is recommended to use key attribute whenever we use map() function.

Using json data

For the fake online REST API, visit json Data

Go to Network tab -> refresh page -> users -> Response to preview response data that we'll use.

Otherwise, you can log it onto console with the following

JSX
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users').then(response =>
console.log(response)
);
}

Interacting with these APIs is very dynamic. We don't need to hard code all these information in HTML/JS/CSS.

Interacting with back-end

In real life, we'd get the information from back-end rather than hard coding the user information.

Life cycle methods

Examples:

JS
componentDidCatch()
componentDidMount()
componentDidUpdate()
componentWillMount()
componentWillReceiveProps()
componentWillUnmount()
componentWillUpdate()
// ...

These methods get called by React at different stages of when component gets rendered.

JS
componentDidMount()

When component mounts; when React puts component on page onto DOM for the first time.

JSX
componentDidMount() {
// Makes URL request
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users2 => this.setState
({users: users2}));
}

users2 is an array of users in json file. Then, users in constructor is set to users2 from json data

Creating functional component

Component can be built with class like we already have (App component)

JSX
class App extends Component {}

Or component can be built with function like

JSX
export const CardList = props => {
// ...
return <div></div>
}

Both always returns JSX.

card.component.jsx (functional component)

Create src/components/card-list/card-list.component.jsx.

.jsx is a naming convention. .js will also work, but .jsx is more explicit.

JSX
import React from 'react';
export const CardList (props) => {
console.log(props);
return <div>{props.children}</div>;
}

Component takes a props as argument.

Note: each card component does not care about other card.

import in app.js

JS
import { CardList } from "./components/card-list/card-list.component"

Usage

JSX
<CardList name="Ellis">
<h1>First</h1>
<h1>Second</h1>
</CardList>

In console it will log {name: "Ellis", children: Array(2)}}

Children are what you pass between the tag.

Add styling

Create src/components/card-list/card-list.styles.css

CSS
.card-list {
width: 85vw;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 20px;
}

import and create className

JSX
import React from "react"
import "./card-list.styles.css"
export const CardList = props => {
// console.log(props);
return <div className="card-list">{props.children}</div>
}

Put users in CardList in app.js

JSX
<CardList>
{this.state.users.map(user => (
<h1 key={user.id}> {user.name}</h1>
))}
</CardList>

CardList/Card Component

Make CardList component responsible to each Card List

app.js

JSX
class App extends React.Component {
// ...
render() {
return (
<div className="App">
<CardList users={this.state.users} />
</div>
)
}
}
JSX
<CardList users=>{this.state.users} />

Passing users as prop

card-list.component.jsx

JSX
import React from "react"
import "./card-list.styles.css"
export const CardList = props => (
<div className="card-list">
{props.users.map(user => (
<h1 key={user.id}> {user.name}</h1>
))}
</div>
)

Create card.component.jsx

Create _src/components/card/card.component.jsx__

JSX
import React from "react"
export const Card = props => (
<div>
<h1> {props.user.name} </h1>
</div>
)

Add styling

Create src/components/card/card.component.jsx

CSS
.card-container {
display: flex;
flex-direction: column;
background-color: lavender;
border: 1px solid grey;
border-radius: 5px;
padding: 25px;
cursor: pointer;
-moz-osx-font-smoothing: greyscale;
backface-visibility: translateZ(0);
transition: trasnform 0.25s ease-out;
}
.card-container:hover {
transform: scale(1.05);
}

card-list.component.jsx

JSX
import React from "react"
import { Card } from "../card/card.component"
import "./card-list.styles.css"
export const CardList = props => (
<div className="card-list">
{props.users.map(user => (
<Card key={user.id} user={user} />
))}
</div>
)

Update card.component.jsx

JSX
import React from "react"
import "./card.styles.css"
export const Card = props => (
<div className="card-container">
<img
alt="user"
src={`https://robohash.org/${props.user.id}?set=set4&size=180x180`}
/>
<h2> {props.user.name} </h2>
<p> {props.user.email} </p>
</div>
)

By creating CardList and Card component, the app has more flexibility as CardList can take different props. And it can improve performance.

State vs Props

As we've had users as state

JSX
this.state = {
users: [],
}

and passed onto CardList

JSX
<CardList users={this.state.users} />

i.e. CardList component receives the state as prop. This is what generally happens; state often changes because of user interaction.

Changes in state is trickling down the tree in virtual DOM (recall one way data flow). State can turn into props/components.

In our case, users gets passed down to be used as prop/component. Take a look into the debugging tool:

1 1 1 2

App(root of the tree) has the user array as state. But as it's passed down to CardList, it turns into prop.

Again, as the state changes the appropriate component receives the new prop and re-render.

1. Create state

JSX
this.state = {
// ...
searchBox: "",
}
JSX
render() {
return (
<div className="App">
<input type='search' placeholder='search users' onChange={e => console.log(e) }/>
</div>
);
}
JSX
onChange={...} // JSX

onChange does not directly modify the DOM unlike onchange used in HTML. Instead, it runs the synthetic event, whenever input changes in the input box.

With synthetic event, React DOM recognizes change and intercepts to change the state.

When we log onto console, it will yield the whole native object. What we're interested is in e.target.value.

JSX
<input
type="search"
placeholder="search users"
onChange={e => console.log(e.target.value)}
/>

Result:

1 3

3. Store input into state

JSX
<input
type="search"
placeholder="search users"
onChange={e => this.setState({ searchBox: e.target.value })}
/>

Try logging into console

JSX
<input
type="search"
placeholder="search users"
onChange={e => {
this.setState({ searchBox: e.target.value })
console.log(this.state)
}}
/>

On console, the state shows search field is one letter behind.

It is because setState() is a asynchronous call that does not happen immediately-- it schedules and batches the work for optimization. i.e. React figures out the best time to update (declarative).

To fix this, use a callback:

JSX
<input
type="search"
placeholder="search users"
onChange={e => {
this.setState({ searchBox: e.target.value }, () => console.log(this.state))
}}
/>

4. Search user

Note that the web app re-renders after the change of state, here, after onChange. So we can add the code under render method.

JSX
render(){
// Retrive attribute from state and store into const variable
const { users, searchBox} = this.state;
// Above is same as..
// const uesrs = this.state.users;
// const searchBox = this.state.searchBox;
const filteredUsers = users.filter(user =>
user.name.toLowerCase().includes(searchBox.toLowerCase())
);
}
JSX
users.filter()

filter() returns array from function we pass in.

Now we need to pass in filteredUsers into CardList

JSX
<CardList users={filteredUsers} />

5. Make SearchBox a component

Create the following

src/components/search-box/search-box.component.jsx

src/components/search-box/search-box.styles.css

search-box.component.jsx

JSX
import React from "react"
import "./search-box.styles.css"
export const SearchBox = ({ placeholder, handleChange }) => (
<input type="search" placeholder={placeholder} onChange={handleChange} />
)

search-box.styles.css

CSS
input {
position: relative;
margin-bottom: 30px;
-webkit-transition: width 0.4s ease-in-out;
transition: width 0.4s ease-in-out;
width: 130px;
border: 3px solid aquamarine;
padding: 8px;
}
/* When input field gets focus, change width*/
input:focus {
width: 70%;
}

By creating search box as a component, this can be re-used without duplicated codes.

Note: component file doesn't have scope to state or life cycle method, but the functional component doesn't need them. It just needs to render.

Important: There is no state inside SearchBox component because our root of the virtual DOM, App, has two components, CardList and SearchBox.

Using state inside SearchBox disables CardList to find out what's going on in SearchBox. This is because of unidirectional data flow. This is why structuring state is very important in React.

Writing methods

From

JSX
return (
//...
<SearchBox
placeholder="search users"
handleChange={e => this.setState({ searchBox: e.target.value })}
/>
)

We can create method

JSX
class App extends Component {
// ...
handleChange(e) {
this.setState({ searchBox: e.target.value })
}
}

And call with

JSX
return <SearchBox placeholder="search users" handleChange={this.handleChange} />

Here, we encounter an error that this is undefined. We need to explicitly create a scope in constructor with the following

JSX
constructor() {
this.handleChange = this.handleChange.bind(this);
}
JSX
.bind(this);

.bind() is a method on any function that returns a new function where context of this is set to whatever we pass to it.

But it'd be hassle to do it each time in the constructor. There's an alternative with arrow function:

JSX
class App extends Component {
// ...
handleChange = e => {
this.setState({ searchBox: e.target.value })
}
}

It binds the this context to the place where they were defined in the first place.

When App component is instantiated, JS defines all methods of component including the arrow function. Then, it binds automatically.

This is called lexical scope; a variable defined outside a function can be accessible inside another function defined after the variable declaration.

Similar idea on the console:

1 4

More on function scope

JSX
class App extends React.Component {
constructor() {
super()
this.handleClick2 = this.handleClick1.bind(this)
}
handleClick1() {
console.log("btn 1 clicked")
}
handleClick3 = () => console.log("btn 3 clicked")
render() {
return (
<div>
<button onClick={this.handleClick1()}>click 1</button>
<button onClick={this.handleClick1}>click 2</button>
<button onClick={this.handleClick2}>click 3</button>
<button onClick={this.handleClick3}>click 4</button>
</div>
)
}
}
  • click 1 calls the function this.handleClick1() and this gets invoked on render(), not on click. This is why we shouldn't call a function on event handler.

  • click 2 has this.handleClick1 assigned to handleClick1(). It prints out on console on click. However, when we change it to

JSX
handleClick1() {
console.log(this);
}
JSX
this.handleClick1()

console out this (app). while..

JSX
this.handleClick

console out undefined

  • click 3 prints out "btn 1 clicked". However, since this one is bound in constructor, it prints this (app) other than undefined.

  • click 4 uses the arrow function so it also prints this (app) instead of undefined

Take away is to use arrow function on any class methods you define that isn't part of React(i.e. render(), componentDidMount()).

Deploying app to github

1. Create repository on github

1 5

1 6

BASH
git remote add origin https://github.com/EllisMin/search-app-.git

2. install gh-pages

BASH
npm i gh-pages

3. Prepare deploy in package.json

JSON
{
"name": "my-app",
// ...
"homepage": "https://ellismin.github.io/search-app",
"dependencies": {
// ...
},
"scripts": {
// ...
"eject": "react-scripts eject",
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
},
// ...
BASH
npm run deploy

4. Push

BASH
git add -A
git commit -m "commitMessage"
git push origin master

5. Set up github page branch

1 7

Now check the website


WRITTEN BY

Keeping a record