Understand common methods of React hooks useState, useEffect, useMemo, useCallback, useContext

Hits: 0

useState

useState receives an initial value and returns a deconstructed [array] . The first is the current state (similar to state), and the second is the update function of the state (similar to setState). The update function is different from react’s setState in that useState’s update function will replace the state instead of merging it.

Usage scenario: The function component needs to use the state value, similar to the state of the [react] class component

import React, { useState } from 'react';

function Example() {
   // declare a new state variable called "count", 0 is the default value 
  const [count, setCount] = useState( 0 );

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
export default Example;

Default value: after useState() parentheses, you can pass in a value or a function, which will only render once

const [ count, setCount ] = useState(() => {
  return props.count || 0
})

Update rendering: When we update the value using useState, the component re-renders. If the incoming update value is unchanged, the component will not re-render.

useReducer

Usage scenario: Since the update function of useState adopts the replacement method, when we want to deal with complex states in function components, such as objects and arrays, etc., using useState is not satisfactory. So we use useReducer to solve this problem

const [state, dispatch] = useReducer(reducer, initialArg, init);

It receives a reducer in the form of (state, action) => newState, and returns the current state and its dispatch method, with initialArg as the initial value.

import React, { useReducer } from  'react' ;
 // Modify a property in a state object. If useState is used, an object must be reassigned, and each property name must be written again 
const initialState = { count : 0 , name : 'name' , age : 1 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {...state, count: state.count + 1};
    case 'decrement':
      return {...state, count: state.count - 1};
    default:
      throw new Error();
  }
}

function Example() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
export default Example;

init is a function that takes initialArg as an input parameter to operate initialArg and return a custom initial value.

import React, { useReducer } from 'react';
function init(initialCount) {
  return {count: initialCount};
}
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}
function Example({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
export default Example;

useEffect

Usage scenario: the useEffect side effect enables function components to have a declaration cycle similar to react. useEffect will be called after each render of the component, useEffect has two parameters, the first is the execution function, and the second is an array []

If you are familiar with the lifecycle functions of React classes, you can think of useEffect Hook as a combination of three functions: componentDidMount, componentDidUpdate and componentWillUnmount

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

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

  /* 
   * Case 1: useEffect has no second parameter
   */ 
  //The useEffect will be executed after the component is initialized and rendered 
  useEffect( () => {
     console .log( "equivalent to life cycle: componentDidMount+componentDidUpdate" )
  });

  /*
   * Case 2: useEffect has a second parameter
   */ 
  //When the second parameter is an empty array: 
  useEffect( () => { 
     console .log( "equivalent to life cycle: componentDidMount" );
  }, []);

  //When the second parameter is the specified state value: 
  useEffect( () => { 
     console .log( "equivalent to life cycle: componentDidMount" )
     console .log( "equivalent to relying on dataSources Lifecycle of state value: componentDidUpdate" )
  }, [dataSources]);

  //Return a function in the execution function: execute the function body during initialization, and execute the returned function when the component is unmounted and unmount 
  useEffect( () => {
     console .log( "equivalent to life cycle: componentDidMount" )
     // Directly in the execution function Use return to return a function, which will be executed when the component is unmount. 
    return  () => {
       console .log( 'equivalent to declaration cycle: componentWillUnmount' );  
    }
  }, []);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
export default Example;

Official tip: Unlike componentDidMount or componentDidUpdate, useEffect is asynchronous, and
effects dispatched by useEffect
will not block the browser from updating the screen, which makes your application appear more responsive. The official recommendation is to use useEffect as much as possible, and effects do not need to be executed synchronously

In individual cases (such as measuring layout, page status value flickering bug), useLayoutEffect is used instead of useEffect to form synchronization and execute immediately after the DOM update is completed, but it will run before the browser does any drawing, blocking the browser. drawing

React.memo and useCallback and useMemo
React.memo
Usage scenario: React.memo is similar to React.PureComponent, which is specially used for pure function components. The function is to shallowly compare internal objects to determine whether to re-render.
import React, { useState, useCallback } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  const [list, setList] = useState([]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>addCount</button>
      <button onClick={() => setList([...list, 1])}>changeList</button>
      <Child list={list} />
    </div>
  );
}
const Child = (props) => {
  console.log("Entered the component child")
  return (
    < div > here is child:list is {props.list.join(',')} </ div >
  )
}
export default Example;

At this point, the Child component will be re-rendered whether it is changing the count state value or the name state value. If we want the Child component to render only when the value passed in by its props, that is, the list changes,
use React.memo to solve it, modify the Child as follows

const Child = React.memo( ( props ) => {
   console .log( "Entering the component child" )
   return (
     < div > Here is the child: {props.list.join(',')} </ div >
  )
})

useCallback

Usage scenario: useCallback is used to cache methods, similar to what is defined in the react component constructor: this.onChange =
this.onChange.bind(this), returning a cached function
There is still a problem in the above example, if the Child component passes in a function As a parameter, since the function is a reference type, each time the Child component is passed in, it is a new function instance, as follows

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

  const handleChange = () => {
    console.log({{EJS0}});
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>addCount</button>
      <button onClick={() => setList([...list, 1])}>changeList</button>
      <Child list={list} handleChange={handleChange} />
    </div>
  );
}
const Child = React.memo((props) => {
  console.log("Entered the component child")
  return (
    < div > here is child:list is {props.list.join(',')} </ div >
  )
})
export default Example;

At this point, the Child component will be re-rendered whether it is to change the count state value or the list state value.
Use useCallback to solve it, and modify the handleChange method as follows:

// The second parameter is an empty array, or it can be specified to depend on a state value, that is, the handleChange method is changed only when the state value changes

const handleChange = useCallback(() => {
  console.log({{EJS1}});
},[]) // or [list]

use Memo

Usage scenario: The useMemo function is used to cache the state value of the operation to be calculated, similar to the calculated property of Vue. The first parameter is a calculation function, and must return a result, returning a cached value, the second parameter is an array [], useMemo execution depends on the state value of the array

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

  const [a, setA] = useState(0)
  const [b, setB] = useState(0)

  const handleChange = useCallback(() => {
    console.log({{EJS2}});
  },[])

  //Use useMemo to cache a calculated value, the execution of the calculation function depends on the state values ​​a and b, and the calculation function is executed when a and b change 
  const memoizedValue = useMemo( () => a + b, [a, b]) ;
   return (
     < div > 
      < p > You clicked {count} times </ p > 
      < button  onClick = {() => setCount(count + 1)}>addCount </ button > 
      < button  onClick = {() => setList([...list, 1])}>changeList </ button > 
      <Child list={list} memoizedValue={memoizedValue} handleChange={handleChange} />
    </div>
  );
}
const Child = React.memo((props) => {
  console.log("Entered the component child")
  return (
    < div > Here is child: list is {props.list.join(',')}, calculate sum: {props.memoizedValue} </ div >
  )
})
export default Example;


useRef

useRef can accept a default value and return a mutable object with a current property;

Usage scenarios:
1. Get the instance of the subcomponent (the subcomponent needs to inherit the component from the react class);

  1. Get a DOM element in the component;

  2. Used as a global variable of a component, the return object of useRef contains a current property, which can be unchanged during the entire component color life cycle, and will not be repeatedly declared due to repeated renders, similar to the react class inheriting the component’s property this. xxx is the same.

Reason: Since the variables saved by useState will trigger the component render, and using useRef to define global variables will not trigger the component render

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

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

  // Get the span of the DOM element
  const spanEl = useRef(null);
  const getSpanRef = () => {
    console.log(2, spanEl.current)
  };
  // Get the react class component instance
  const sunEl = useRef( null );
  const getRefBySun = () => {
     console .log( 1 , sunEl.current)
  };
  // Global variable isClick, the default value of false is assigned to isClick.current, which is equivalent to this.isClick = false 
  const isClick = useRef( false );
  const addCount = () => {
    if (!isClick.current) {
      setCount(count + 1)
      isClick.current = true
    }
  };
  return (
    <>
      <button onClick={addCount}>addCount</button>
      <Sun ref={sunEl} count={count} />
      <span ref={spanEl}>I am span</span>
      <button onClick={getSpanRef}>Get DOM element Span</button>
      <button onClick={getRefBySun}>Get Sun component</button>
    </>
  );
}
// Sun subcomponent
 class  Sun  extends  React . Component {
  render () {
    const { count } = this.props
    return (
      <div>{ count }</div>
    )
  }
}
export default Example;

React.forwardRef:

Usage scenario: It is rarely used. It is used to obtain the DOM element of the child component in the parent component as its own ref, and then manipulate the DOM element of the child component.

import React, { useRef } from 'react';

function Example() {

  //ref
  const inputEl = useRef(null);
  const onButtonClick = () => {
    console.log(inputEl)
    inputEl.current.focus();
  };
  return (
    <>
      <TextInputWithFocusButton ref={inputEl} onButtonClick={onButtonClick} />
      <span>I am span</span>
    </>
  );
}
// child component
const TextInputWithFocusButton = (props) => {
  return (
    <>
      <input ref={props.ref} type="text" />
      <button onClick={props.onButtonClick}>Focus the input</button>
    </>
  );
}
export default Example;

The above code reports an error: Warning: TextInputWithFocusButton: ref is not a prop.

You need to use the React.forwardRef package to inject a new ref, and modify the TextInputWithFocusButton component as follows:

const TextInputWithFocusButton = React.forwardRef((props, ref) => {
  return (
    <>
      <input ref={ref} type="text" />
      <button onClick={props.onButtonClick}>Focus the input</button>
    </>
  );
})

useContext

Usage scenario: useContext needs to be used in combination with React.createContext to solve the problem of data transfer between components, similar to redux.

Use the useContext method

import React, { useRef, useContext } from  'react' ;
 //Context, createContext receives a parameter as default value 
const ThemeContext = React.createContext( 'white' );
 const AgeContext = React.createContext();

function Example() {
  return (
    <>
      <ThemeContext.Provider value={'blue'}>
        <div>
          <ChildOfContext />
        </div>
      </ThemeContext.Provider>
    </>
  );
}
// Subassembly
const ChildOfContext = (props) => {
  console.log("Entered the child component ChildOfContext")
  return (
    <div>
      Here is the child component ChildOfContext
       <GrandChildOfContext />
    </div>
  )
}
// grandchild component
const GrandChildOfContext = (props) => {
  console.log("Entered grandchild component GrandChildOfContext")
  const color = useContext(ThemeContext);
  return (
    <div>
      Here is the child component GrandChildOfContext
      The color is: {color}
    </div>
  )
}
export default Example;

Using the traditional React API method, modify the grandchild component as follows:

const GrandChildOfContext = ( props ) => {
   console .log( "Enter grandchild component GrandChildOfContext" )
   // Use Consumer to get value from context 
  return (
     < ThemeContext.Consumer >
      {value => (
        <div>
        Here is the child component GrandChildOfContext
        The color is: {color}
      </div>
      )}
    </ThemeContext.Consumer>

  )
}

Using the contextType method, only React class components are supported. Modify the grandchild components as follows:

class GrandChildOfContext extends React.Component {
  static contextType = ThemeContext
  render () {
    const color = this.context
    return (
      <div>
        Here is the child component GrandChildOfContext
         The color is: {color}
      </div>
    )
  }
}

Use the useContext method when multiple contexts are nested

import React, { useRef, useContext } from  'react' ;
 //Context, createContext receives a parameter as default value 
const ThemeContext = React.createContext( 'white' );
 const AgeContext = React.createContext();

function Example() {
  return (
    <>
      <ThemeContext.Provider value={'blue'}>
        <div>
          <ChildOfContext />
        </div>
      </ThemeContext.Provider>
    </>
  );
}
// Subcomponents are nested one level Context
const ChildOfContext = (props) => {
  console.log("Entered the child component ChildOfContext")
  return (
    <div>
      Here is the child component ChildOfContext
      <AgeContext.Provider value={21}>
        <div>
          <GrandChildOfContext />
        </div>
      </AgeContext.Provider>
    </div>
  )
}
// grandchild component
const GrandChildOfContext = (props) => {
  console.log("Entered grandchild component GrandChildOfContext")
  const color = useContext(ThemeContext);
  const age = useContext(AgeContext);
  return (
    <div>
      Here is the child component GrandChildOfContext
      The color is: {color}
      Nested Context
      Age is: {age}
    </div>
  )
}
export default Example;

Using the traditional React API method, modify the grandchild component as follows:
// The traditional react API method

const GrandChildOfContext = ( props ) => {
   console .log( "Entered grandchild component GrandChildOfContext" )
   return (
     < ThemeContext.Consumer >
      {color => (
        <div>
          Here is the child component GrandChildOfContext
          The color is: {color}
          <AgeContext.Consumer>
            {age => (
              <div>
              Nested Context
              Age is: {age}
            </div>
            )}
          </AgeContext.Consumer>
      </div>
      )}
    </ThemeContext.Consumer>
  )
}

The contextType method does not support nesting of multiple Contexts. It can be seen that the useContext method is the simplest.

Official reminder: Receive a context object (the return value of React.createContext) and return the current value of the context. The current
context value is determined by the value prop of the closest to the current component in the upper-level component.
When the most recent on top of the component is updated, this Hook triggers a re-render with the latest
context value passed to the MyContext provider. Even if the ancestor uses React.memo or
shouldComponentUpdate, it will re-render when the component itself uses useContext.
Therefore, the more components wrapped in and the more nesting, the greater the impact on sub-components, resulting in too many unnecessary re-rendering, so Context is best used nearby instead of spanning multiple levels of components

Hook usage rules

Hooks are just JavaScript functions, but there are two additional rules for using them:

Hooks can only be called at the outermost level of a function. Don’t call it in loops, conditionals, or subfunctions. Hooks can only be called from React’s function components. Do not call in other
JavaScript functions.

import React, { useState } from 'react';

let isName = true;
function Example() {
   //Error: React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render 
  // Hook can only be called at the outermost layer of the function. Don't call it in loops, conditionals, or subfunctions. 
  const [count, setCount] = useState( 0 );
   if (isName){
     const [name, setName] = useState( 'name' );
    isName = false ;
  }
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

You may also like...

Leave a Reply

Your email address will not be published.