[React]: Basics
React
10/03/2019
Install Create React App
Docs on installing Create React Appnpx create-react-app my-appcd my-app
npx comes with npm(node package manager) version 5.2+
Under "scripts": in package.json, it has list of following commands
npm start
Start the application in my-app directory
npm build
Turn all source code from /src/ into code that browser understands(HTML/CSS) and put into /public/ directory.
npm run build
Creates a optimized build directory with source code. build folder is what we use to deploy the application.
npm test
Runs test code.
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
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
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.
import React, { Component } from "react"
Otherwise, you can also do
class App extends React.Component
Function to class
/* 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.
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
<button onClick={() => this.setState({ string: "Min" })}> Change Text</button>
{ ;() => this.setState} // JavaScript anonymous function that calls this.setState
As soon as the state changes, render() gets called again
app.js
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
<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
constructor() { super(); this.state = { users: [ { name: 'Bob', id: '2' }, { name: 'Peter', id: '3' }, { name: 'John', id: '4' } ] }; }
To access each element
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
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:
componentDidCatch()componentDidMount()componentDidUpdate()componentWillMount()componentWillReceiveProps()componentWillUnmount()componentWillUpdate()// ...
These methods get called by React at different stages of when component gets rendered.
componentDidMount()
When component mounts; when React puts component on page onto DOM for the first time.
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)
class App extends Component {}
Or component can be built with function like
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.
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
import { CardList } from "./components/card-list/card-list.component"
Usage
<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
.card-list { width: 85vw; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; grid-gap: 20px;}
import and create className
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
<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
class App extends React.Component { // ... render() { return ( <div className="App"> <CardList users={this.state.users} /> </div> ) }}
<CardList users=>{this.state.users} />
Passing users as prop
card-list.component.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__
import React from "react"export const Card = props => ( <div> <h1> {props.user.name} </h1> </div>)
Add styling
Create src/components/card/card.component.jsx
.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
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
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
this.state = { users: [],}
and passed onto CardList
<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:
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.
Adding Search Box
1. Create state
this.state = { // ... searchBox: "",}
2. Create input for search box
render() { return ( <div className="App"> <input type='search' placeholder='search users' onChange={e => console.log(e) }/> </div> ); }
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.
<input type="search" placeholder="search users" onChange={e => console.log(e.target.value)}/>
Result:
3. Store input into state
<input type="search" placeholder="search users" onChange={e => this.setState({ searchBox: e.target.value })}/>
Try logging into console
<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:
<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.
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()) );}
users.filter()
filter() returns array from function we pass in.
Now we need to pass in filteredUsers into CardList
<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
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
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
return ( //... <SearchBox placeholder="search users" handleChange={e => this.setState({ searchBox: e.target.value })} />)
We can create method
class App extends Component { // ... handleChange(e) { this.setState({ searchBox: e.target.value }) }}
And call with
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
constructor() { this.handleChange = this.handleChange.bind(this);}
.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:
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:
More on function scope
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
handleClick1() { console.log(this); }
this.handleClick1()
console out this (app). while..
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
git remote add origin https://github.com/EllisMin/search-app-.git
2. install gh-pages
npm i gh-pages
3. Prepare deploy in package.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" }, // ...
npm run deploy
4. Push
git add -Agit commit -m "commitMessage"git push origin master
5. Set up github page branch
Now check the website