Introduction to React

This post demonstrates the concepts of Reactand its general use. The basic operations of React will be demonstrated.

Overview

Introducing to JSX

Consider this variable declaration:

const element = <h1>Hello, world!</h1>;

This funny tag syntax is neither a string nor HTML.

It is called JSX, and it is a syntax extension to JavaScript. We recommend using it with React to describe what the UI should look like. JSX may remind you of a template language, but it comes with the full power of JavaScript.

JSX produces React “elements”. We will explore rendering them to the DOM in the next section. Below, you can find the basics of JSX necessary to get you started.

Why JSX

React embraces the fact that rendering logic is inherently coupled with other UI logic: how events are handled, how the state changes over time, and how the data is prepared for display.

Instead of artificially separating technologies by putting markup and logic in separate files, React separates concerns with loosely coupled units called “components” that contain both. We will come back to components in a further section, but if you’re not yet comfortable putting markup in JS, this talk might convince you otherwise.

React doesn’t require using JSX, but most people find it helpful as a visual aid when working with UI inside the JavaScript code. It also allows React to show more useful error and warning messages.

  • In the example below, we declare a variable called name and then use it inside JSX by wrapping it in curly braces:

    const element = <h1>Hello, {name}</h1>;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    

​ You can put any valid JavaScript expression inside the curly braces in JSX. For example, 2 + 2, user.firstName, or formatName(user) are all valid JavaScript expressions.

  • In the example below, we embed the result of calling a JavaScript function, formatName(user), into an <h1> element。

    function formatName(user) {
      return user.firstName + ' ' + user.lastName;
    }
    
    const user = {
      firstName: 'Harper',
      lastName: 'Perez'
    };
    
    const element = (
      <h1>
        Hello, {formatName(user)}!  
      </h1>
    );
    
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    

JSX is an Expression Too

After compilation, JSX expressions become regular JavaScript function calls and evaluate to JavaScript objects.

This means that you can use JSX inside of if statements and for loops, assign it to variables, accept it as arguments, and return it from functions:

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;  }
  return <h1>Hello, Stranger.</h1>;}

Specifying Attributes with JSX

You may use quotes to specify string literals as attributes:

const element = <div tabIndex="0"></div>;

You may also use curly braces to embed a JavaScript expression in an attribute:

const element = <img src={user.avatarUrl}></img>;

Specifying Children with JSX

If a tag is empty, you may close it immediately with />, like XML:

const element = <img src={user.avatarUrl} />;

JSX tags may contain children:

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

Conditional rendering:

if-else conditional rendering:
const loadData = () =>{
    if (isLoading) {
        return<div>loading...</div>
    }
    return <div>Loading finished, here is the place to display data.</div>
}
ternary operator conditional rendering:
const loadData = () =>{
	return isLoading ? (<div>loading...</div>) : (<div>Loading finished, here is the place to display data.</div>)
}
&& conditional rendering
const loadData = () =>{
    return isLoading && (<div>loading...</div>)
}

List Rendering

  • Use map() method to render a list of data
  • cautious: add key entity when rendering a list, and the value of key should be unique
  • avoid use index as key
const songs = [
    {id: 1, name: 'My love'},
    {id: 2, name: "Transpassing"},
    {id: 3, name: 'Uptown Funk'},
]

To render this list in the page, use <ul>{songs.map(item => <li key={item.id}>item.name</li>)}</ul> in the JSX.

Style bound

To bound style with CSS, we should first create a class name in the corresponding tag. Then, we can import the targeted CSS file. For example, we can create index.css, then, write:

.list{
test-aligh: center
}

In index.js, we can add parameters in our

  • tag. <ul classname='list' style={ {color: 'red', backgroundColor: 'skyblue'}}>{songs.map(item => <li key={item.id}>item.name</li>)}</ul>

After the three examples, the generated code should look like:

// Use JSX to transfer JS variables into the JSX
const name = "Danny"
const age = 19
const isLoading = true

// ternary operator conditional rendering:
const loadData = () =>{
    return isLoading ? (<div>loading...</div>) : (<div>Loading finished, here is the place to display data.</div>)
}

// List rendering:
const songs = [
    {id: 1, name: 'My love'},
    {id: 2, name: "Transpassing"},
    {id: 3, name: 'Uptown Funk'},
]

const title1 = (
    <div className="title">
        Hello JSX, {name}, age: {age}
        <h1>
            Conditional rendering:
            {loadData()}
        </h1>
        <ul className="list" style={ {color: 'red', backgroundColor: 'skyblue'} }>
            {songs.map(item => <li key={item.id}>{item.name}</li>)}
        </ul>
    </div>

)

// render react element
ReactDOM.render(title1, document.getElementById('root'))

Then when running npm start, the page rendering in the browser looks like:
请添加图片描述

React component Creation

use function to creation component
  • The component using JS function is called: function component
  • Function components must have returning value
  • The first letter of the component name must be upper-case, React uses this to distinguish the differences between component and a normal React element.

For example:

funcfunction Hello() {
    return(
        <div>This is the first function component</div>
    )
}

Or you can simplify the syntax:

const Hello = () => <div>This is the first component</div>
use class to create a React component
  • Class component: the component used by ES6
  • First letter of a class name must be upper case
  • Class component should inherit React.Component super class to get the method provided by ***super()***.
  • Class component must provide render() method
  • render() must have returning value
class HelloClass extends React.Component {
    render() {
        return (
            <div>This is the first class component</div>
        )
    }
}

In the index.html, we can add a tag <div id='hello'></div>, then we can add:

ReactDOM.render(<HelloClass />, document.getElementById('hello'))

Write a independent JS file to include class component

  • Create Hello.js
  • Import React in to Hello.js
  • Write component or class
  • Export this component
  • Import this component in index.js
  • Render this component

In the new Hello.js:

import React from 'react'

// make a new component
class Hello_com extends React.Component{
    render() {
        return <div>This is the first imported component</div>
    }
}

// export component
export default Hello_com

Then, we need to import this file into index.js and render it directly.

// import Hello component
import Hello_com from "./Hello";

ReactDOM.render(<Hello_com />, document.getElementById('hello_com') )

React Event handling

Event binding:
  1. Syntax: ***on + event name = {event handling process}***, such as onClick={()=>{}}
  2. Cautious: React event handling uses Hump nomenclature, such as: onMouseEnter, onFocus…
  • Event binding as a class:

    // event handling
    class eventHandle extends React.Component {
        handleClick(){
            console.log("Clicking.... ")
        }
        // Envent handler
        render() {
            return (
                <button onClick={this.handleClick}>Hit me!!</button>
            )
        }
    }
    
  • Event binding as a function:

    function event_handle(){
        function handleClick(){
            console.log("Clicking.... ")
        }
        return(
            <button onClick={handleClick}>Hit me!!</button>
        )
    }
    
Event Object
  1. We can get event object through the parameter of the event handling function

  2. Event objects in React are called **Synthetic event (object) **

  3. Synthetic events: They are compatible with all the browsers, there is no need to worry about the problem of cross-browser problem.

    Assume we have a function to prevent the default behavior of the browser:

    function handleClick(e){
        // prevent the default behavior of the browser
        e.preventDefault()
        console.log("Clicking.... ")
    }
    

    if we click on the button which calls this function:

     <a href="https://www.baidu.com" onClick={handleClick}>
                Click this to not jump to baidu
            </a>
    

    Then, it will not jump to baidu.

Stateful component && Stateless component

  • Function component is called Stateless component, while class component is called Stateful component
  • State is relevant to data
  • Function component has no state, only responsible to show Static data
  • Class component has its own state, responsible for refreshing UI, which let the data flow
State && setState in component
Usage of state :
  • state is relevant to data

  • state is private, only accessible in the inner structure of component

  • Use this.state to get its value

    class Hello_com extends React.Component{
        state = {
            count:0
        }
        render() {
            return <div>This is the first stateful imported component, {this.state.count}</div>
        }
    }
    
setState() to change state
  • state can be changed

  • Syntax: this.setState({value to be changed})

  • Cautious: Do not try to change the value in state, which is prohibited

  • effect of setState(): 1. Change State 2. Refresh UI

  • It is a reflection of data driven view

    class Hello_com extends React.Component {
        state = {
            count: 0,
            test: "this will not change"
        }
    
        render() {
            return (
                <div>
                    <h1><a>
                        This is the first stateful imported component
                    </a>
                        couter: {this.state.count}
                        <h2>
                            test: {this.state.test}
                        </h2>
                    </h1>
                    <button onClick={() => {
                        this.setState({
                            count: this.state.count + 1
                        })
                    }}>+1
                    </button>
                </div>
            )
        }
    }
    
Detach the event handling function out from the JSX structure
  • We can write a independent function onIncrement() in the class

    onIncrement(){
            this.setState({
                count: this.state.count + 1
            })
        }
    

    Then, the button in the JSX structure becomes:

    <button onClick={this.onIncrement}>+1</button>
    

    However, when running the program, there is an Error:

    Error1

Error when calling onIncrement()

This is because originally we are using abbreviated function calling method:

<button onClick={() => {this.setState({count: this.state.count + 1})}}>+1 in the event handling method, and ()=>{} this kind of method calling does not contain this inside, thus, the this of this abbreviated calling method will find its parent method render(), and this super class has a setState() method. However, when we detach the method to outside, the value of this changes.

Solutions
  1. Arrow function:

    Arrow function doesn’t contain this.

    <button onClick={() => this.onIncrement()}>+1</button>
    
  2. Function.prototype.bind():

    constructor() {
            super();
            this.state = {
                count: 0,
                test: "this will not change"
            }
            this.onIncrement = this.onIncrement.bind(this)
        }
    

    Then, in the JSX below, we can use:

    <button onClick={this.onIncrement}>+1</button>
    
  3. Use arrow function as class instantiated method:

    onIncrement=() => {
            console.log("clicking....")
            this.setState({
                count: this.state.count + 1
            })
        }
    

Form Processing

Controlled component
  • Form element in HTML is importable, which is said to have its own state.
  • However, the changeable State value usually stores in state variable, and it can only be modified through setState() method.
  • React binds State with from element value, and it can control form value through State value.
  • Controlled component: The form element whose value is controlled by React.
Steps:
  1. Add a State to represent the value of the form element. (In charge of the source of the form element)

     state = {
            txt: 'aaaaaaaaa'
        }
    
    
  2. Bind the element with Change event, and set the value of the form element to be the value of State. (In charge of the change of the element value)

    <input type="text" value={this.state.txt} onChange={this.handleChange} />
    

    Event handler:

    handleChange = e =>{
            this.setState(
                {
                    txt: e.target.value
                }
            )
        }
    

    For text, text area, pull down menu, we can operate through e.target.value, for check box, we can operate on e.target.checked. Below is an example of operations on controlled elements:

    import React from 'react'
    
    // Form processing
    
    class Form_process extends React.Component {
        state = {
            txt: 'aaaaaaaaa',
            content: '',
            city: 'bj',
            isChecked: false
        }
    
        // Handle change in the text input
        handleChange = e => {
            this.setState(
                {
                    txt: e.target.value
                }
            )
        }
    
        // Handle change in the text area
        handleContent = e => {
            this.setState(
                {
                    content: e.target.value
                }
            )
        }
    
        // Handle pull down menu
        handleCity = e => {
            this.setState(
                {
                    city: e.target.value
                }
            )
        }
    
        // Handle the change in check box
        handleChecked = e =>{
            this.setState(
                {
                    isChecked: e.target.checked
                }
            )
        }
    
        render() {
            return (
                <div>
                    {/*{ text }*/}
                    <input type="text" value={this.state.txt} onChange={this.handleChange}/>
                    <br/>
    
                    {/* text area */}
                    <textarea value={this.state.content} onChange={this.handleContent}/>
                    <br/>
    
                    {/*  pull down menu  */}
                    <select value={this.state.city} onChange={this.handleCity}>
                        <option value="sh">Shanghai</option>
                        <option value="bj">Beijing</option>
                        <option value="gz">Guangzhou</option>
                    </select>
    
                    {/*  check box  */}
                    <input type="checkbox" checked={this.state.isChecked} onChange={this.handleChecked}/>
                </div>
            )
        }
    }
    
    export default Form_process
    

    Then we can handle changes in the form on the page:

    在这里插入图片描述

To integrate the event handler into one method:

  1. Add name entity in form element which is the same as the state name to distinguish from others.

  2. Obtain the corresponding value according to the type of form element.

  3. Change the corresponding state in Change event handler through [name].

    class Form_process extends React.Component {
        state = {
            txt: 'aaaaaaaaa',
            content: '',
            city: 'bj',
            isChecked: false
        } 
    
    handleForm = e => {
    
            // obtain the current DOM object
            const target = e.target
    
            // Obtain the corresponding value through its type
            const value = target.type === 'checkbox'
                ? target.checked
                : target.value
    
            // Obtain the name
            const name =target.name
    
            this.setState({
                [name]: value
            })
        }
     
     render() {
            return (
                <div>
                    {/*{ text }*/}
                    <input type="text" name='txt' value={this.state.txt} onChange={this.handleForm}/>
                    <br/>
    
                    {/* text area */}
                    <textarea value={this.state.content} name='content' onChange={this.handleForm}/>
                    <br/>
    
                    {/*  pull down menu  */}
                    <select value={this.state.city} name='city' onChange={this.handleForm}>
                        <option value="sh">Shanghai</option>
                        <option value="bj">Beijing</option>
                        <option value="gz">Guangzhou</option>
                    </select>
    
                    {/*  check box  */}
                    <input type="checkbox" checked={this.state.isChecked} name='isChecked' onChange={this.handleForm}/>
                </div>
            )
        }
    }
    

Communication Between Components

Components are independent units, which can only own its own data. When we break our functions into several parts, we modularize our functions, so that we can better achieve our functions. During this process, different components unavoidably need to share their properties with each other. To realize these functions, we need to break out the encapsulation of each component, to let them communicate with out side. This process is called components comminication.

Props

  • Components are encapsualated
  • Function of props: Receive the data passed to components
  • Passing data: Add properties to tags
  • Receive data: Function components receive data through parameter props, Class components via this.props.
  • We can pass any type of data through props
  • props can only be read, not be modified
  • When using class components, if we write a constructor, we should pass props to super(). Otherwise, we cannot get props via constructor

Example of passing props though function:

import React from "react";
import ReactDOM from 'react-dom';

/*
    props
 */

//Receive data

const Hello = (props) => {
    console.log(this.props)

    return(
        <div>
            <h1>props:  </h1>
        </div>
    )
}

// Passing data
ReactDOM.render(<Hello name='Jack' age={19}/>, document.getElementById('props'))

Example of passing props through class:

class Props extends React.Component {
    render() {
        // console.log(this.props)
        constructor(props) {
        // Recommend to pass props to Constructor
        super(props);
    }
        this.props.fn() // passing a function
        return(
            <div>
                <h1>props: {this.props.age}</h1>
            </div>
        )
    }
}

// Passing data through props
ReactDOM.render(<Props name='Jack' age={19} colors={['red','blue','green']} fn={() => console.log('This is a function')}/>, document.getElementById('props'))

Three means of communication between components

  1. Parent => child

    1. Parent provide state data
    2. Add parameter to child component tag, whose value is the data in state
    3. Child component receives the data passed by parent component
    class Parent extends React.Component{
        state = {
            firstName: 'Wong',
            lastName: 'Yeah'
        }
        render() {
            return(
                <div>
                    Pass data to child components: <Child firstName={this.state.firstName} lastName={this.state.lastName}/>
                </div>
            )
        }
    }
    
    const Child = props => {
        console.log("Child props",props)
        return(
            <div className="child">
                <p>Child component, receiving data passed by pappa: {props.firstName}</p>
            </div>
        )
    }
    
  2. Child => Parent

    Use callback function, parent component provides callback, child function use that callback function, set the parameter of the callback function to be the data to be passed

    1. parent provides a callback function (To receive data)
    2. pass this function to child component by using props
    3. child component call callback function by using props
    4. pass data back to parent component through child component’s parameter
    // data from Child component to parent component:
    class Parent1 extends React.Component {
        state = {
            parentMsg: ''
        }
    
        // Provide callback function, to receive data
        getChildMsg = data => {
            console.log('receiving msg from child: ', data)
            this.setState({
                parentMsg: data
            })
        }
    
        render() {
            return(
                <div className='parent'>
                    Pappa component: {this.state.parentMsg}
                    <Child1 getMsg={this.getChildMsg} /> // pass call back function to child
                </div>
            )
        }
    }
    
    // child class
    class Child1 extends React.Component{
        state = {
            msg: 'save Me!'
        }
        handleClick = () => {
            // call the callback function sent by parent component
            this.props.getMsg(this.state.msg)
        }
        
        render() {
            return(
                <div className='child'>
                    child component: {' '}
                    <button onClick={this.handleClick}>
                        hit me to send data to parent component!!!
                    </button>
                </div>
            )
        }
    
  3. Children => Children

    1. Promote the sharing states to the nearest public parent component, and let the public parent component control this state
    2. Strategy: Promote the sharing states
    3. responsibilities of parent component:
      1. Provide sharing state
      2. Provide the function to operate the sharing state

    The below example is to show a simple counter on browse:

    // Children to Children
    class Counter extends React.Component {
        // Provide sharing state
        state = {
            count: 0
        }
    
        // Provide a method to change state
        onIncrement = () => {
            console.log("Heeeeeaaaa!!!!")
            this.setState({
                count: this.state.count + 1
            })
        }
    
        render() {
            return (
                <div>
                    <Child2 count={this.state.count}/>
                    <Child3 onIncrement={this.onIncrement}/>
                </div>
            )
        }
    }
    
    const Child2 = props => {
        return <h1>Counter: {props.count}</h1>
    }
    
    const Child3 = (props) => {
        return <button onClick={() => props.onIncrement()}>+1</button>
    }
    

请添加图片描述
ReactCounter

Context

What should we do if we need to transfer data from App which is the outer node to the inner node Child?

  • We can apply Context

  • Function: Transfer data across Components (Such as: Theme, Language)

请添加图片描述

Transfer data across components

Steps:

  1. Call React.createContext() to create Provider (to provide data) and Consumer (to receive data) these two components

    const {Provider, Consumer} = React.createContext
    
  2. Use Provider as the Parent Node

    <Provider>
        <div className='context'>
            <Node />
        </div>
    </Provider>
    
  3. Set a value property, which is represented as the data to be passed

    <Provider value='pink'>
    
  4. Call Consumer to receive data

    <Consumer>
        {data => <span>I am a Child Node -- {data}</span>}
    </Consumer>
    

For example, in the below components, between Provider and Consumer, there are nested components which are respectively Node && SubNode, thus, it is not so clear to transfer data through this nested structure by passing props layer by layer. We use Provider and Consumer to transfer data.

import React from 'react'
import * as ReactDOM from "react-dom";

/*
    Context
 */

const {Provider, Consumer} = React.createContext()

class Context extends React.Component {
    render() {
        return (
            <Provider value='pink'>
                <div className='context'>
                    <Node/>
                </div>
            </Provider>
        )
    }
}

const Node = props => {
    return (
        <div className='node'>
            <SubNode/>
        </div>
    )
}

const SubNode = props => {
    return (
        <div className='subnode'>
            <Child/>
        </div>
    )
}

const Child = props => {
    return (
        <div className='child'>
            <Consumer>
                {data => <span>I am a Child Node -- {data}</span>}
            </Consumer>
        </div>
    )
}

ReactDOM.render(<Context/>, document.getElementById('context'))

Children property of props

  • Children: Represents the child Node of component tag. When the component tag has a child Node, props has this property.
  • Children is the same as normal props, the value of which can be any kind of value (Text, React const, component, even function)
// Children property
const Children = props => {
    console.log(props)
    return(
        <div>
            <h1>The children node of this component: </h1>
            {props.children}
        </div>
    )
}

// Children is Text 
//ReactDOM.render(<Children>I am a children </Children>, document.getElementById('Children'))
// Children is a tag
ReactDOM.render(<Children>
        <p>I am a children node, which is a p tag</p>
    </Children>,
    document.getElementById('Children'))

Props Check

When passing Props across components, users cannot know its type. If a user pass a props to a worry position which requires another specific type, the error cannot be prevented by observation.

  • We can install an extra props checking tool via npm i props-types.

  • Import prop-type in the project

  • Use ComponentName.propTrpes = {} to add checking rules for components

  • We can point our checking rules via PropTypes object.

    import PropTypes from 'prop-types'
    
    // Using PropTypes to check props type error
    const CheckProp = props => {
        const arr = props.colors
        const list = arr.map((item, index) => <li key={index}>{item.name}</li> )
        return <ul>{list}</ul>
    }
    
    // add props checking rules
    CheckProp.propTypes = {
        // If the type of color is not an array, error will be generated, user can know exactly which type color needs
        colors: PropTypes.array
    }
    ReactDOM.render(<CheckProp colors={19} />, document.getElementById('props'))
    

    We can see that colors in the above code requires an array, however, we give a type of Integer, thus, error code generates:

请添加图片描述

Type error generated by PropTypes

​ if we give the colors an array instead of an Integer, the error disappears. For example:

ReactDOM.render(<CheckProp colors={['red', 'blue']} />, document.getElementById('props'))
  • Rule for Props checking

    1. Types: array, bool, func, number, objectstring
    2. React component type: element
    3. Required: isRequired

    If we need to add proptype checking for one class for example:

    // Add props type checking
    // type of a:      number
    // type of fn:     func and isRequired
    // type of tag:    element
    // type of filter: object: ({area:'Shanghai', price: 1999})
    
    CheckProp.propTypes = {
        a : PropTypes.number,
        fn : PropTypes.func.isRequired,
        tag: PropTypes.element,
        filter: PropTypes.shape({
            area: PropTypes.string,
            price: PropTypes.number
        })
    }
    

Lifecycle of a component

  • Definition: The process of which Components are created and then loaded to run in a particular webpage, then destroyed if not being used.
  • Every sage of a lifecycle is accompanied by some functions, these functions are called hook function
  • Function of hook function: to provide chance for developers to handle components in multiple stages.
  • Only class components have lifecycle
Three stages of components’ lifeCycle:

请添加图片描述

LifeCycle of a component
  1. On creation:

    Sequence of the three hook functions:

    Constructor => render() => componentDidMount

    Hook functionTrigger TimeFunction
    constructorFirst to trigger when the component is created1. Initialize state. 2. Bind this for event handler
    renderEvery time to refresh the componentRender UI (Cautions: Do not call setState() )
    componentDidMountAfter DOM rendering1. Send HttpRequest (ajax) 2. Operate on DOM
  2. On refreshing (Updating):

  • Triggers: 1. SetState(), forceUpdate(), when the component receive newprops.

  • Any kind of the above three function changes, the components will be refreshed

  • Sequence:

    render() => componentDidUpdate()

Hook functionTrigger TimeFunction
renderEvery time to refresh the componentRender UI (The same as on create stage)
componentDIdUpdateAfter components are updated1. Send HttpRequest 2. Operate on DOM 3. If need to call setState() to refresh state, it must be put in the same if condition, otherwise, it will be nested
  1. On Unmount:

    • Triggers: When the components disappear from a page
    Hook functionTrigger timefunction
    componentWillUnmountUninstallExecute wiping out jobs

For example: In the below instance, HitTom is a parent component, you can hit a button to hit Tom, at the same time, a timer will start, if the counter is larger than 3, 'Tom is dead!!!" will be displayed, meanwile, child component will be uninstalled and the timer will be stopped by ComponentWillUmount() .

// Hook functions
class HitTom extends React.Component {

    // First to execute
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
    }

    handleClick = () => {
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return (
            <div>
                {
                    this.state.count > 3
                        ? <p>Tom is dead!!!</p>
                        : <LifeCycle count={this.state.count}/>
                }
                <button onClick={this.handleClick}>Hit Tom!!!</button>
            </div>
        )
    }
}

class LifeCycle extends React.Component {

    // on refreshing
    componentDidMount() {
        // Open a Timer
        this.timerId = setInterval(() => {
            console.log('Timer on going......')
        }, 500)
    }

    render() {
        return <h1>Times of which Tom was beaten: {this.props.count}</h1>
    }

    // Clear timer when the component is uninstalled
    componentWillUnmount() {
        console.warn('LifeCycle hook function: componentWillUnmount')
        clearInterval(this.timerId)
    }
}

High-level components

High-level Component: (HOC, higher-Order Component) is a function, which receives the components to be wrapped, and returns the enhanced component. The inner of HOC creates a class component, in which provides the state and logic resolving codes that can be reused, and it passes the state and logic to the WrappedComponent via prop.

  • Purpose: To realize recycle of states and logic.
  • Utilizes Wrapping mode:
  • For example: Phone, acquires protection. Phone shell: Provide protection. High-level components is like a phone shell, enhances functions of components by wrapping them.

Steps:

  1. Write a function, which is headed with with.

  2. Set the designated function, which needs to be fronted by a capital letter (To use as the component to be rendered).

  3. Create a class inside the function, which provides the logic and state to be reused, and return it.

  4. In this component, it renders the passed-in component, and passes the state via props.

  5. Call this HOC, and pass in the necessary component, get the enhanced component via the return value, and render it in the webpage.

In the below example, we realizes a function to capture the position of our mouse:

import React from "react";
import ReactDom from 'react-dom'
import img from './img/cat.jpg'

/*
* High level component
*
*/

// Create a high-level component
function withMouse(WrappedComponent){
    class Mouse extends React.Component{
        // State of the mouse:
        state = {
            x: 0,
            y:0
        }

    	// mouse position handler
        handleMouseMove = e => {
            this.setState({
                x:e.clientX,
                y:e.clientY
            })
        }

        // control the state of the mouse
        componentDidMount() {
            window.addEventListener('mousemove', this.handleMouseMove)
        }

        // Untie the event handler
        componentWillUnmount() {
            window.removeEventListener('mousemove', this.handleMouseMove)
        }

        // Pass the state by props to the function to be wrapped
        render(){
            return <WrappedComponent {...this.state} />
        }
    }
    return Mouse
}

// call the HOC
const Position = props => (
    <p>
        Current Mouse Position: (x: {props.x}, y: {props.y})
    </p>
)

// obtain enhanced component
const MousePosition = withMouse(Position)

// Cat catch mouse
const Cat = props => (
    <img src={img} alt="" style={{
        position: "absolute",
        top: props.y +350
        ,
        left: props.x - 64,
        width: 100,
        height: "auto"
    }} />
)

const CatWithMouse = withMouse(Cat)

class HOC extends React.Component {
    render() {
        return(
            <div>
                <h1>
                    high-level component
                    {/* Enhanced component */}
                    <MousePosition />
                    <CatWithMouse />
                </h1>
            </div>
        )
    }
}

ReactDom.render(<HOC />, document.getElementById('HOC'))import React from "react";
Setup DisplayName for passed in components
  • Problems when using HOC: The names of passed in functions are the same, It is impossible to distinguish different function when they are all passed in the same high-level component.
  • Reason: In the default condition, React uses component name as DisplayName.
  • Solution: Setup displayName for high-level components to distinguish them when debugging. (In React dev tool)
// Set display name
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`

// getDisplayName of function
function getDisplayName(WrappedComponent) {
    return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

React Component Sum

  • Communication between components are the necessary part in building a React app
  • props is the key to parameter transferring between components
  • State promotion is the usual way of communication
  • Hook Function can be executed at particular time to get special jobs done
  • Simple model of components: (state, props) => UI

Using the State Hook

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

We’ll start learning about Hooks by comparing this code to an equivalent class example.

  • Equivalent Class Example

If you used classes in React before, this code should look familiar:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

The state starts as { count: 0 }, and we increment state.count when the user clicks a button by calling this.setState(). We’ll use snippets from this class throughout the page.

Hooks and Function Components

As a reminder, function components in React look like this:

const Example = (props) => {
  // You can use Hooks here!
  return <div />;
}

or this:

function Example(props) {
  // You can use Hooks here!
  return <div />;
}

You might have previously known these as “stateless components”. We’re now introducing the ability to use React state from these, so we prefer the name “function components”.

Hooks don’t work inside classes. But you can use them instead of writing classes.

What’s a Hook?

Our new example starts by importing the useState Hook from React:

import React, { useState } from 'react';
function Example() {
  // ...
}

What is a Hook? A Hook is a special function that lets you “hook into” React features. For example, useState is a Hook that lets you add React state to function components. We’ll learn other Hooks later.

When would I use a Hook? If you write a function component and realize you need to add some state to it, previously you had to convert it to a class. Now you can use a Hook inside the existing function component. We’re going to do that right now!

Declaring a State Variable

In a class, we initialize the count state to 0 by setting this.state to { count: 0 } in the constructor:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {      count: 0    };  }

In a function component, we have no this, so we can’t assign or read this.state. Instead, we call the useState Hook directly inside our component:

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"  const [count, setCount] = useState(0);

What does calling useState do? It declares a “state variable”. Our variable is called count but we could call it anything else, like banana. This is a way to “preserve” some values between the function calls — useState is a new way to use the exact same capabilities that this.state provides in a class. Normally, variables “disappear” when the function exits but state variables are preserved by React.

What do we pass to useState as an argument? The only argument to the useState() Hook is the initial state. Unlike with classes, the state doesn’t have to be an object. We can keep a number or a string if that’s all we need. In our example, we just want a number for how many times the user clicked, so pass 0 as initial state for our variable. (If we wanted to store two different values in state, we would call useState() twice.)

What does useState return? It returns a pair of values: the current state and a function that updates it. This is why we write const [count, setCount] = useState(). This is similar to this.state.count and this.setState in a class, except you get them in a pair.

Now that we know what the useState Hook does, our example should make more sense:

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"  const [count, setCount] = useState(0);

We declare a state variable called count, and set it to 0. React will remember its current value between re-renders, and provide the most recent one to our function. If we want to update the current count, we can call setCount.

Reading State

When we want to display the current count in a class, we read this.state.count:

  <p>You clicked {this.state.count} times</p>

In a function, we can use count directly:

  <p>You clicked {count} times</p>
Updating State

In a class, we need to call this.setState() to update the count state:

  <button onClick={() => this.setState({ count: this.state.count + 1 })}>    Click me
  </button>

In a function, we already have setCount and count as variables so we don’t need this:

  <button onClick={() => setCount(count + 1)}>    Click me
  </button>
Recap

Let’s now recap what we learned line by line and check our understanding.

import React, { useState } from 'react'; 2:
 3:  function Example() {
 4:    const [count, setCount] = useState(0); 5:
 6:    return (
 7:      <div>
 8:        <p>You clicked {count} times</p>
 9:        <button onClick={() => setCount(count + 1)}>10:         Click me
11:        </button>
12:      </div>
13:    );
14:  }
  • Line 1: We import the useState Hook from React. It lets us keep local state in a function component.
  • Line 4: Inside the Example component, we declare a new state variable by calling the useState Hook. It returns a pair of values, to which we give names. We’re calling our variable count because it holds the number of button clicks. We initialize it to zero by passing 0 as the only useState argument. The second returned item is itself a function. It lets us update the count so we’ll name it setCount.
  • Line 9: When the user clicks, we call setCount with a new value. React will then re-render the Example component, passing the new count value to it.
Tip: What Do Square Brackets Mean?

You might have noticed the square brackets when we declare a state variable:

  const [count, setCount] = useState(0);

The names on the left aren’t a part of the React API. You can name your own state variables:

  const [fruit, setFruit] = useState('banana');

This JavaScript syntax is called “array destructuring”. It means that we’re making two new variables fruit and setFruit, where fruit is set to the first value returned by useState, and setFruit is the second. It is equivalent to this code:

  var fruitStateVariable = useState('banana'); // Returns a pair
  var fruit = fruitStateVariable[0]; // First item in a pair
  var setFruit = fruitStateVariable[1]; // Second item in a pair

When we declare a state variable with useState, it returns a pair — an array with two items. The first item is the current value, and the second is a function that lets us update it. Using [0] and [1] to access them is a bit confusing because they have a specific meaning. This is why we use array destructuring instead.

Tip: Using Multiple State Variables

Declaring state variables as a pair of [something, setSomething] is also handy because it lets us give different names to different state variables if we want to use more than one:

function ExampleWithManyStates() {
  // Declare multiple state variables!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

In the above component, we have age, fruit, and todos as local variables, and we can update them individually:

function handleOrangeClick() {
    // Similar to this.setState({ fruit: 'orange' })
    setFruit('orange');
  }

You don’t have to use many state variables. State variables can hold objects and arrays just fine, so you can still group related data together. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.

Using the Effect Hook

The Effect Hook lets you perform side effects in function components:

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:  useEffect(() => {    // Update the document title using the browser API    document.title = `You clicked ${count} times`;  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

This snippet is based on the counter example from the previous part, but we added a new feature to it: we set the document title to a custom message including the number of clicks.

Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.

Tip

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

There are two common kinds of side effects in React components: those that don’t require cleanup, and those that do. Let’s look at this distinction in more detail.

Effects Without Cleanup

Sometimes, we want to run some additional code after React has updated the DOM. Network requests, manual DOM mutations, and logging are common examples of effects that don’t require a cleanup. We say that because we can run them and immediately forget about them. Let’s compare how classes and Hooks let us express such side effects.

Example Using Classes

In React class components, the render method itself shouldn’t cause side effects. It would be too early — we typically want to perform our effects after React has updated the DOM.

This is why in React classes, we put side effects into componentDidMount and componentDidUpdate. Coming back to our example, here is a React counter class component that updates the document title right after React makes changes to the DOM:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {    document.title = `You clicked ${this.state.count} times`;  }  componentDidUpdate() {    document.title = `You clicked ${this.state.count} times`;  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

Note how we have to duplicate the code between these two lifecycle methods in class.

This is because in many cases we want to perform the same side effect regardless of whether the component just mounted, or if it has been updated. Conceptually, we want it to happen after every render — but React class components don’t have a method like this. We could extract a separate method but we would still have to call it in two places.

Now let’s see how we can do the same with the useEffect Hook.

Example Using Hooks

We’ve already seen this example at the top of this page, but let’s take a closer look at it:

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {    document.title = `You clicked ${count} times`;  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.

Why is useEffect called inside a component? Placing useEffect inside the component lets us access the count state variable (or any props) right from the effect. We don’t need a special API to read it — it’s already in the function scope. Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution.

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.

Detailed Explanation

Now that we know more about effects, these lines should make sense:

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
}

We declare the count state variable, and then we tell React we need to use an effect. We pass a function to the useEffect Hook. This function we pass is our effect. Inside our effect, we set the document title using the document.title browser API. We can read the latest count inside the effect because it’s in the scope of our function. When React renders our component, it will remember the effect we used, and then run our effect after updating the DOM. This happens for every render, including the first one.

Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. This is intentional. In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale. Every time we re-render, we schedule a different effect, replacing the previous one. In a way, this makes the effects behave more like a part of the render result — each effect “belongs” to a particular render. We will see more clearly why this is useful later on this page.

Tip

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen. This makes your app feel more responsive. The majority of effects don’t need to happen synchronously. In the uncommon cases where they do (such as measuring the layout), there is a separate useLayoutEffect Hook with an API identical to useEffect.

Effects with Cleanup

Earlier, we looked at how to express side effects that don’t require any cleanup. However, some effects do. For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we don’t introduce a memory leak! Let’s compare how we can do it with classes and with Hooks.

Example Using Classes

In a React class, you would typically set up a subscription in componentDidMount, and clean it up in componentWillUnmount. For example, let’s say we have a ChatAPI module that lets us subscribe to a friend’s online status. Here’s how we might subscribe and display that status using a class:

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {    ChatAPI.subscribeToFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }  componentWillUnmount() {    ChatAPI.unsubscribeFromFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }  handleStatusChange(status) {    this.setState({      isOnline: status.isOnline    });  }
  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

Notice how componentDidMount and componentWillUnmount need to mirror each other. Lifecycle methods force us to split this logic even though conceptually code in both of them is related to the same effect.

Note

Eagle-eyed readers may notice that this example also needs a componentDidUpdate method to be fully correct. We’ll ignore this for now but will come back to it in a later section of this page.

Example Using Hooks

Let’s see how we could write this component with Hooks.

You might be thinking that we’d need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up:

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {    function handleStatusChange(status) {      setIsOnline(status.isOnline);    }    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    // Specify how to clean up after this effect:    return function cleanup() {      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    };  });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

Why did we return a function from our effect? This is the optional cleanup mechanism for effects. Every effect may return a function that cleans up after it. This lets us keep the logic for adding and removing subscriptions close to each other. They’re part of the same effect!

When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time. We’ll discuss why this helps avoid bugs and how to opt out of this behavior in case it creates performance issues later below.

Note

We don’t have to return a named function from the effect. We called it cleanup here to clarify its purpose, but you could return an arrow function or call it something different.

Recap

We’ve learned that useEffect lets us express different kinds of side effects after a component renders. Some effects might require cleanup so they return a function:

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

Other effects might not have a cleanup phase, and don’t return anything.

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

The Effect Hook unifies both use cases with a single API.

Tips for Using Effects

Tip: Use Multiple Effects to Separate Concerns

One of the problems we outlined in the Motivation for Hooks is that class lifecycle methods often contain unrelated logic, but related logic gets broken up into several methods. Here is a component that combines the counter and the friend status indicator logic from the previous examples:

class FriendStatusWithCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }
  // ...

Note how the logic that sets document.title is split between componentDidMount and componentDidUpdate. The subscription logic is also spread between componentDidMount and componentWillUnmount. And componentDidMount contains code for both tasks.

So, how can Hooks solve this problem? Just like you can use the State Hook more than once, you can also use several effects. This lets us separate unrelated logic into different effects:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

Hooks let us split the code based on what it is doing rather than a lifecycle method name. React will apply every effect used by the component, in the order they were specified.

Explanation: Why Effects Run on Each Update

If you’re used to classes, you might be wondering why the effect cleanup phase happens after every re-render, and not just once during unmounting. Let’s look at a practical example to see why this design helps us create components with fewer bugs.

Earlier on this part, we introduced an example FriendStatus component that displays whether a friend is online or not. Our class reads friend.id from this.props, subscribes to the friend status after the component mounts, and unsubscribes during unmounting:

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

But what happens if the friend prop changes while the component is on the screen? Our component would continue displaying the online status of a different friend. This is a bug. We would also cause a memory leak or crash when unmounting since the unsubscribe call would use the wrong friend ID.

In a class component, we would need to add componentDidUpdate to handle this case:

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate(prevProps) {    // Unsubscribe from the previous friend.id    ChatAPI.unsubscribeFromFriendStatus(      prevProps.friend.id,      this.handleStatusChange    );    // Subscribe to the next friend.id    ChatAPI.subscribeToFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

Forgetting to handle componentDidUpdate properly is a common source of bugs in React applications.

Now consider the version of this component that uses Hooks:

function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

It doesn’t suffer from this bug. (But we also didn’t make any changes to it.)

There is no special code for handling updates because useEffect handles them by default. It cleans up the previous effects before applying the next effects. To illustrate this, here is a sequence of subscribe and unsubscribe calls that this component could produce over time:

// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // Run first effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // Run next effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // Run next effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect

This behavior ensures consistency by default and prevents bugs that are common in class components due to missing update logic.

Tip: Optimizing Performance by Skipping Effects

In some cases, cleaning up or applying the effect after every render might create a performance problem. In class components, we can solve this by writing an extra comparison with prevProps or prevState inside componentDidUpdate:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

This requirement is common enough that it is built into the useEffect Hook API. You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

In the example above, we pass [count] as the second argument. What does this mean? If the count is 5, and then our component re-renders with count still equal to 5, React will compare [5] from the previous render and [5] from the next render. Because all items in the array are the same (5 === 5), React would skip the effect. That’s our optimization.

When we render with count updated to 6, React will compare the items in the [5] array from the previous render to items in the [6] array from the next render. This time, React will re-apply the effect because 5 !== 6. If there are multiple items in the array, React will re-run the effect even if just one of them is different.

This also works for effects that have a cleanup phase:

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes

In the future, the second argument might get added automatically by a build-time transformation.

Note

If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders. Learn more about how to deal with functions and what to do when the array changes too often.

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.

If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often. Also, don’t forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.

We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.

Rules of Hooks

Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in depth below.)

Only Call Hooks from React Functions

Don’t call Hooks from regular JavaScript functions. Instead, you can:

  • ✅ Call Hooks from React function components.
  • ✅ Call Hooks from custom Hooks

By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.

Explanation

As we learned earlier, we can use multiple State or Effect Hooks in a single component:

function Form() {
  // 1. Use the name state variable
  const [name, setName] = useState('Mary');

  // 2. Use an effect for persisting the form
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });

  // 3. Use the surname state variable
  const [surname, setSurname] = useState('Poppins');

  // 4. Use an effect for updating the title
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });

  // ...
}

So how does React know which state corresponds to which useState call? The answer is that React relies on the order in which Hooks are called. Our example works because the order of the Hook calls is the same on every render:

// ------------
// First render
// ------------
useState('Mary')           // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm)     // 2. Add an effect for persisting the form
useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle)     // 4. Add an effect for updating the title

// -------------
// Second render
// -------------
useState('Mary')           // 1. Read the name state variable (argument is ignored)
useEffect(persistForm)     // 2. Replace the effect for persisting the form
useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle)     // 4. Replace the effect for updating the title

// ...

As long as the order of the Hook calls is the same between renders, React can associate some local state with each of them. But what happens if we put a Hook call (for example, the persistForm effect) inside a condition?

  // 🔴 We're breaking the first rule by using a Hook in a condition
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

The name !== '' condition is true on the first render, so we run this Hook. However, on the next render the user might clear the form, making the condition false. Now that we skip this Hook during rendering, the order of the Hook calls becomes different:

useState('Mary')           // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm)  // 🔴 This Hook was skipped!
useState('Poppins')        // 🔴 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle)     // 🔴 3 (but was 4). Fail to replace the effect

React wouldn’t know what to return for the second useState Hook call. React expected that the second Hook call in this component corresponds to the persistForm effect, just like during the previous render, but it doesn’t anymore. From that point, every next Hook call after the one we skipped would also shift by one, leading to bugs.

This is why Hooks must be called on the top level of our components. If we want to run an effect conditionally, we can put that condition inside our Hook:

  useEffect(function persistForm() {
    // 👍 We're not breaking the first rule anymore
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

Note that you don’t need to worry about this problem if you use the provided lint rule. But now you also know why Hooks work this way, and which issues the rule is preventing.

Principles in React

Asynchronization of SetState()

  • Cautious: When using this function, the setState() on the back is independent of the results of the front setState(), although it can be called multiple times in a class.

  • We can call multiple times of setState(), but it will only trigger render() once.

  • Recommended syntax:

     this.setState((state, props) => {
                return {
                    count: state.count + 1
                }
                })
    
    • Usage: this.setState((state,props) => {})
    • parameter state: the newest state
    • parameter props: the newest props

Second parameter of setState()

  • Circumstance: When the page refreshing is done, it will be executed

  • syntax: setState(updater, [callback])

    onIncrement=() => {
        console.log("clicking....")
        // Asynchronization, not recommended
        // this.setState({
        //     count: this.state.count + 1
        // })
    
        // Recommended:
        this.setState((state, props) => {
            return {
                count: state.count + 1
            }
            },
            // execute after the page is refreshed
            () => {
            console.log('Refreshing accomplished: ', this.state.count)
                document.title = 'Title after refreshing: ' + this.state.count
            })
        console.log(this.state.count) // less 1 
    }
    

请添加图片描述
请添加图片描述

The picture shows that call back function happens right after the rendering is done.

Transfer process of JSX syntax
  • JSX is a simplized syntax of createElement()
  • JSX syntax is compiled as createElement() by @babel/preset-react
  • React element: is a object, to describe what you see on the screen
  • JSX syntax => createElement() => React element

React Router

Modern front-end applications are usually SPA (Single Page Application), that is to say the application has only one page of HTML. Due to its light-weighted body, low pressure for server, it is popular. To better utilize the technique that one HTML controls multiple pages, front-end router is born.

  • Function of front-end router : navigate from one view (page) to another view (page)
  • front-end router is series of rules which defines mapping. In React, it is demonstrated as the relation between URL and Components.

Basic steps:

  1. Install via npm i react-router-dom
  2. Import three core component: Router/Route/Link
  3. Wrap the whole component through Router component
  4. Make a navigate menu via link component (Routing entrance)

This is the basic router of React:

import React from "react";
import ReactDOM from 'react-dom'


import {BrowserRouter as Router, Route, Link} from "react-router-dom";

const First = () => <p>This is the content of the first page</p>

// Wrap the whole component by <Router></Router>
class BasicRouter extends React.Component {
    render() {
        return(
            <Router>
                <div>
                    <h1>This is the basic react router</h1>

                    {/* designate the entrance of the router */}
                    <Link to="/first"> Page one</Link>

                    {/*  designate the exit of router  */}
                    <Route path="/first" component={First}/>
                </div>
            </Router>
        )
    }
}

ReactDOM.render(<BasicRouter />, document.getElementById('Router'))

Router description

  • Router component: It wraps the whole application, and one React App only need to use it once.

  • Two Basic Router: BrowsRouter && HarshRouter.

  • HashRouter: Realize through the hash value of the URL (localhost:3000/#/first)

  • Link: To use as the entrance (href) of the router (a tag)

    // to: pathname (location.pathname) in the browser url braket  
    <Link to="/first"> Page one</Link>
    
  • Route: To show the related information of the designated router

// path: Routing rule
// component: The components to be described
// can decide where the routing component is placed
<Route path="/first" component={First}/>

Logical Routing

  • Circumstance: When clicking the login button, it jumps to the login page. After the login is done, it jumps back.
  • Programmable routing: Page routing through JS code.
  • push(path): Jump to a particular page, path indicates the path to be jumped to
  • go(n): Forward or back to a particular page, n indicates the number of pages to move (-1 is represented as move back to the last page)

The below example shows a simple page containing the function of login and return to home page. When hitting the button Go to the login page, it displays the login page:

LoginPage1

Login page after clicking the button

Then, when hitting Hit me to Login~ it displays the home page:

请添加图片描述

Home page after login
import React from "react";
import ReactDOM from "react-dom"

import {BrowserRouter as Router, Route, Link} from "react-router-dom";

class Login extends React.Component {

    handleLogin = () => {
        // routing jump
        this.props.history.push('/home')

    }

    render() {
        return (
            <div>
                <p>
                    This is the login page!!
                </p>
                <button onClick={this.handleLogin}>
                    Hit me to Login~
                </button>
            </div>
        )
    }
}

const Home = props => {

    const handleBack = () => {
        // go(-1) indicates to go to the last page
        props.history.go(-1)
    }
    return (
        <div>
            <h2>
                This is the home page
            </h2>
            <button onClick={handleBack}>Return to the login page~</button>
        </div>
    )
}


const AppLogin = () => (
    <Router>
        <div>
            <h1>
                programmable routing:
            </h1>
            <Link to="/login">
                Go to the login page
            </Link>
            <Route path="/login" component={Login}/>
            <Route path="/home" component={Home}/>
        </div>
    </Router>
)

export default AppLogin

Default Route

  • The default path is: /

    <Route path="/" component={Home} />
    

    However, when we use other routes, this default router will be filtered out. This is because React router is a approximate string matching, the routers will be found as long as the string is the beginning of the routing pattern.

    Pathpathname to be matched
    /all the pathes
    /first/first or /first/a or /first/a/b …

    To get rid of this condition, we need apply exact matching:

  • Add exact entity in Route component, it will become exact matching mode

  • Exact matching: Only when path is exactly the same as pathname, it will be matched to the router

    // now, this component will only match pathname="/" this exact condition
    <Route exact path="/" component=... />
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值