React Hooks Common Use Scenarios (Summary)

React Hooks Common Use Scenarios (Summary)

Preface

React launched the new feature React Hooks in version v16.8. In my opinion, using React Hooks has the following advantages over previous class components:

  • The code is more readable. The code logic of the same function was originally split into different lifecycle functions, which makes it difficult for developers to maintain and iterate. React Hooks can aggregate the functional codes for easier reading and maintenance.
  • The component tree becomes shallower. In the original code, we often use HOC/render props to reuse component states, enhance functions, etc., which undoubtedly increases the number of component tree layers and rendering. In React Hooks, these functions can be achieved through powerful custom Hooks.

In this article, we will give examples based on the usage scenarios to help you understand and skillfully use most of the features of React Hooks.

The blog github address is: https://github.com/fengshi123/blog

1. State Hook

1. Basic usage

function State(){
  const [count, setCount] = useState(0);
  return (
      <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
              Click me
          </button>
      </div>
  )
}

2. Update

There are two ways to update: direct update and functional update. The differences in their application scenarios are:

  • Directly update the value that does not depend on the old state;
  • Functional updates depend on the value of the old state;
// Update setState(newCount) directly;

// Functional update setState(prevCount => prevCount - 1);

3. Achieve the merger

Unlike the setState method in class components, useState does not automatically merge the updated object, but directly replaces it. We can use the functional setState combined with the spread operator to achieve the effect of merging and updating objects.

setState(prevState => {
  // You can also use Object.assign
  return {...prevState, ...updatedValues};
});

4. Lazy initialization state

The initialState parameter only takes effect in the initial rendering of the component and is ignored in subsequent renderings. Its application scenario is: when creating the initial state is expensive, for example, it needs to be obtained through complex calculations; then you can pass in a function to calculate and return the initial state in the function. This function is only called during the initial rendering:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

5. Some key points

(1) Unlike this.setState in a class, Hook updates a state variable by always replacing it instead of merging it;
(2) It is recommended to use multiple state variables instead of a single state variable, because the state replacement logic is not the merging logic, and it is conducive to the subsequent extraction of related state logic;
(3) When the update function of the State Hook is called and the current state is passed in, React will skip the rendering of the child component and the execution of the effect. (React uses the Object.is comparison algorithm to compare state.)

Effect Hook

1. Basic usage

function Effect(){
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log(`You clicked ${count} times`);
  });

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

2. Clear operation

To prevent memory leaks, the cleanup function is executed before the component is uninstalled; if the component is rendered multiple times (usually the case), the previous effect is cleared before the next effect is executed, that is, the return function in the previous effect is executed first, and then the non-return function in this effect is executed.

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // Clear subscription subscription.unsubscribe();
  };
});

3. Execution period

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect do not block the browser from updating the screen, which makes your app look more responsive; (componentDidMount or componentDidUpdate will block the browser from updating the screen)

4. Performance optimization

By default, React will delay calling the effect each time it waits for the browser to finish rendering the screen; however, if certain values ​​have not changed between two re-renders, you can tell React to skip calling the effect by passing an array as the second optional parameter of useEffect: As shown below, if the count value has not changed between two renderings, then the effect will be skipped after the second rendering;

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Update only if count changes

5. Simulate componentDidMount

If you want to run the effect only once (only when the component is mounted and unmounted), you can pass an empty array ([]) as the second parameter, as shown below. The principle is the same as described in point 4 performance optimization;

useEffect(() => {
  .....
}, []);

6. Best Practices

It can be hard to remember which props and state a function outside of an effect uses, which is why you usually want to declare the functions it needs inside an effect.

// bad, not recommended function Example({ someProp }) {
  function doSomething() {
    console.log(someProp);
  }

  useEffect(() => {
    doSomething();
  }, []); // 🔴 This is unsafe (it calls `doSomething` which uses `someProp`)
}

// good, recommended function Example({ someProp }) {
  useEffect(() => {
    function doSomething() {
      console.log(someProp);
    }

    doSomething();
  }, [someProp]); // ✅ Safe (our effect only uses `someProp`)
}

If for some reason you can't move a function into an effect, there are a few other options:

  • You could try moving that function outside of your component. That way, the function will definitely not depend on any props or state, and will not need to appear in the dependency list;
  • As a last resort, you can add the function as an effect dependency but wrap its definition in the useCallback Hook. This ensures that it does not change across renders unless its own dependencies change;

It is recommended to enable the exhaustive-deps rule in eslint-plugin-react-hooks, which will issue warnings and provide repair suggestions when adding incorrect dependencies;

// 1. Install the plugin npm i eslint-plugin-react-hooks --save-dev

// 2. eslint configuration {
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

7. Some key points

(1) The useEffect Hook can be considered as a combination of componentDidMount, componentDidUpdate, and componentWillUnmount.
(2) In React's class component, the render function should not have any side effects; generally speaking, it is too early to perform operations here, and we basically want to perform our operations after React updates the DOM.

3. useContext

A method used to handle multi-level data transfer. In the previous component tree, when a cross-level ancestor component wants to pass data to a grandchild component, in addition to passing props down layer by layer, we can also use the React Context API to help us do this. The usage examples are as follows: (1) Use the React Context API to create a Context outside the component

import React from 'react';
const ThemeContext = React.createContext(0);
export default ThemeContext;

(2) Use Context.Provider to provide a Context object that can be shared by child components

import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import ContextComponent1 from './ContextComponent1';

function ContextPage () {
  const [count, setCount] = useState(1);
  return (
    <div className="App">
      <ThemeContext.Provider value={count}>
        <ContextComponent1 />
      </ThemeContext.Provider>
      <button onClick={() => setCount(count + 1)}>
              Click me
      </button>
    </div>
  );
}

export default ContextPage;

(3) The useContext() hook function is used to introduce the Context object and obtain its value

// Subcomponent, use grandchild component in subcomponent import React from 'react';
import ContextComponent2 from './ContextComponent2';
function ContextComponent () {
  return (
    <ContextComponent2 />
  );
}
export default ContextComponent;


// Grandchild component, use the Context object value in the grandchild component import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
function ContextComponent () {
  const value = useContext(ThemeContext);
  return (
    <div>useContext:{value}</div>
  );
}
export default ContextComponent;

4. useReducer

1. Basic usage

Scenarios where useState is more suitable: for example, when the state logic processing is complex and contains multiple sub-values, or when the next state depends on the previous state; examples are shown below

import React, { useReducer } from 'react';
interface stateType {
  count: number
}
interface actionType {
  type: string
}
const initialState = { count: 0 };
const reducer = (state:stateType, action:actionType) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};
const UseReducer = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div className="App">
      <div>useReducer Count:{state.count}</div>
      <button onClick={() => { dispatch({ type: 'decrement' }); }}>useReducer reduce</button>
      <button onClick={() => { dispatch({ type: 'increment' }); }}>useReducer increase</button>
    </div>
  );
};

export default UseReducer;

2. Lazy initialization of state

interface stateType {
  count: number
}
interface actionType {
  type: string,
  paylod?: number
}
const initCount = 0 
const init = (initCount:number)=>{
  return {count:initCount}
}
const reducer = (state:stateType, action:actionType)=>{
  switch(action.type){
    case 'increment':
      return {count: state.count + 1}
    case 'decrement':
      return {count: state.count - 1}
    case 'reset':
      return init(action.paylod || 0)
    default:
      throw new Error();
  }
}
const UseReducer = () => {
  const [state, dispatch] = useReducer(reducer,initCount,init)

  return (
    <div className="App">
      <div>useReducer Count:{state.count}</div>
      <button onClick={()=>{dispatch({type:'decrement'})}}>useReducer reduce</button>
      <button onClick={()=>{dispatch({type:'increment'})}}>useReducer increase</button>
      <button onClick={()=>{dispatch({type:'reset',paylod:10 })}}>useReducer add</button>
    </div>
  );
}
export default UseReducer;

5. Memo

As shown below, when the parent component re-renders, the child component will also re-render, even if the child component's props and state have not changed.
import React, { memo, useState } from 'react';

// Child component const ChildComp = () => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

// Parent component const Parent = () => {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>Click to increase</div>
      <ChildComp/>
    </div>
  );
};

export default Parent;

Improvement: We can use memo package to solve the above problem; but it only solves the situation where the parent component does not pass parameters to the child component and the parent component passes simple type parameters to the child component (such as string, number, boolean, etc.); if complex properties are passed, useCallback (callback event) or useMemo (complex properties) should be used

// Child component const ChildComp = () => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

6. useMemo

Assume the following scenario: the parent component passes the info object property when calling the child component. When the parent component button is clicked, the console prints out the information about the child component being rendered.

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

// Child component const ChildComp = (info:{info:{name: string, age: number}}) => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

// Parent component const Parent = () => {
  const [count, setCount] = useState(0);
  const [name] = useState('jack');
  const [age] = useState(11);
  const info = { name, age };

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>Click to increase</div>
      <MemoChildComp info={info}/>
    </div>
  );
};

export default Parent;

Analysis of the reasons:

  • Click the parent component button to trigger the parent component to re-render;
  • When the parent component is rendered, the line const info = { name, age } will regenerate a new object, causing the value of the info property passed to the child component to change, which in turn causes the child component to be re-rendered.

solve:

Use useMemo to wrap the object attributes. useMemo has two parameters:

  • The first parameter is a function, the returned object points to the same reference, and no new object is created;
  • The second parameter is an array. The function of the first parameter will return a new object only when the variables in the array change.
import React, { memo, useMemo, useState } from 'react';

// Child component const ChildComp = (info:{info:{name: string, age: number}}) => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

// Parent component const Parent = () => {
  const [count, setCount] = useState(0);
  const [name] = useState('jack');
  const [age] = useState(11);
  
  // Use useMemo to wrap object properties const info = useMemo(() => ({ name, age }), [name, age]);

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>Click to increase</div>
      <MemoChildComp info={info}/>
    </div>
  );
};

export default Parent;

7. useCallback

Continuing with the example in Chapter 6, suppose you need to pass the event to the child component, as shown below. When you click the parent component button, you will find that the console prints out information about the child component being rendered, indicating that the child component has been re-rendered.

import React, { memo, useMemo, useState } from 'react';

// Child component const ChildComp = (props:any) => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

// Parent component const Parent = () => {
  const [count, setCount] = useState(0);
  const [name] = useState('jack');
  const [age] = useState(11);
  const info = useMemo(() => ({ name, age }), [name, age]);
  const changeName = () => {
    console.log('Output name...');
  };

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>Click to increase</div>
      <MemoChildComp info={info} changeName={changeName}/>
    </div>
  );
};

export default Parent;

Analyze the reasons:

  • Clicking the parent component button changes the value of the count variable in the parent component (the state value of the parent component), which in turn causes the parent component to re-render;
  • When the parent component is re-rendered, the changeName function is re-created, that is, the changeName property passed to the child component has changed, causing the child component to render;

solve:
Modify the changeName method of the parent component and wrap it with a useCallback hook function. The useCallback parameter is similar to useMemo.

import React, { memo, useCallback, useMemo, useState } from 'react';

// Child component const ChildComp = (props:any) => {
  console.log('ChildComp...');
  return (<div>ChildComp...</div>);
};

const MemoChildComp = memo(ChildComp);

// Parent component const Parent = () => {
  const [count, setCount] = useState(0);
  const [name] = useState('jack');
  const [age] = useState(11);
  const info = useMemo(() => ({ name, age }), [name, age]);
  const changeName = useCallback(() => {
    console.log('Output name...');
  }, []);

  return (
    <div className="App">
      <div>hello world {count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>Click to increase</div>
      <MemoChildComp info={info} changeName={changeName}/>
    </div>
  );
};

export default Parent;

8. useRef

The following are two usage scenarios of useRef:

1. Point to DOM element

As shown below, a variable created using useRef points to an input element and focuses the input after the page is rendered:

import React, { useRef, useEffect } from 'react';
const Page1 = () => {
  const myRef = useRef<HTMLInputElement>(null);
  useEffect(() => {
    myRef?.current?.focus();
  });
  return (
    <div>
      <span>UseRef:</span>
      <input ref={myRef} type="text"/>
    </div>
  );
};

export default Page1;

2. Storing variables

The role of useRef in react hook, as the official website says, is like a variable, similar to this, it is like a box, you can store anything. createRef returns a new reference each time it renders, while useRef returns the same reference each time, as shown in the following example:

import React, { useRef, useEffect, useState } from 'react';
const Page1 = () => {
    const myRef2 = useRef(0);
    const [count, setCount] = useState(0)
    useEffect(()=>{
      myRef2.current = count;
    });
    function handleClick(){
      setTimeout(()=>{
        console.log(count); // 3
        console.log(myRef2.current); // 6
      },3000)
    }
    return (
    <div>
      <div onClick={()=> setCount(count+1)}>Click count</div>
      <div onClick={()=> handleClick()}>View</div>
    </div>
    );
}

export default Page1;

9. useImperativeHandle

Usage scenario: The entire DOM node is obtained through ref, and useImperativeHandle can be used to control the exposure of only some methods and properties instead of the entire DOM node.

10. useLayoutEffect

Its function signature is the same as useEffect, but it calls the effect synchronously after all DOM changes, so it is not shown here.

useLayoutEffect is executed at the same time as componentDidMount and componentDidUpdate of the Class component you normally write;
useEffect will start another task scheduling after the update is completed, that is, after the method in point 1 is executed, and execute useEffect in the next task scheduling;

Summarize

In this article, we will give examples based on the usage scenarios, hoping to help you understand and skillfully use most of the features of React Hooks.

This concludes this article about the use of common scenarios of React Hooks (summary). For more content related to common scenarios of React Hooks, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • React hooks pros and cons
  • 5 things to note when writing React components using hooks
  • React hooks introductory tutorial
  • Example code for developing h5 form page based on react hooks and zarm component library configuration
  • React uses Hooks to simplify state binding of controlled components
  • Master React's most exciting new feature this year - React Hooks in 30 minutes
  • Record a complete react hooks practice
  • In-depth understanding and use of React Hooks
  • How React Hooks Work

<<:  How to let DOSBox automatically execute commands after startup

>>:  Basic installation tutorial of mysql decompression package

Recommend

How to write configuration files and use MyBatis simply

How to write configuration files and use MyBatis ...

Detailed explanation of uniapp's global variable implementation

Preface This article summarizes some implementati...

Mysql experiment: using explain to analyze the trend of indexes

Overview Indexing is a skill that must be mastere...

Attributes and usage of ins and del tags

ins and del were introduced in HTML 4.0 to help au...

JS operation object array to achieve add, delete, modify and query example code

1. Introduction Recently, I helped a friend to ma...

How to add indexes to MySQL

Here is a brief introduction to indexes: The purp...

Native JS to implement breathing carousel

Today I will share with you a breathing carousel ...

How to modify mysql to allow remote connections

Regarding the issue of MySQL remote connection, w...

Detailed explanation of nodejs built-in modules

Table of contents Overview 1. Path module 2. Unti...

Solutions to VMware workstation virtual machine compatibility issues

How to solve VMware workstation virtual machine c...

Vue state management: using Pinia instead of Vuex

Table of contents 1. What is Pinia? 2. Pinia is e...

How to use multi-core CPU to speed up your Linux commands (GNU Parallel)

Have you ever had the need to compute a very larg...

Docker container time zone error issue

Table of contents background question Problem ana...

Canonical enables Linux desktop apps with Flutter (recommended)

Google's goal with Flutter has always been to...

JavaScript article will show you how to play with web forms

1. Introduction Earlier we introduced the rapid d...