Common pitfalls of using React Hooks

Common pitfalls of using React Hooks

React Hooks is a new feature introduced in React 16.8 that allows us to use state and other features without using Class. The problem that React Hooks aims to solve is state sharing. It is the third state logic reuse solution after render-props and higher-order components, and will not cause the JSX nesting hell problem.

Why Hooks?

Before introducing Hooks, I would like to first tell you about the component creation methods of React. One is class components and the other is pure function components. The React team hopes that components will not become complex containers, but should be just pipelines for data flow. Developers can combine pipelines as needed. This means that components are best written as functions rather than classes. .

Function components are more convenient for separating business logic codes and reusing components than class components. Function components are also lighter than class components. Before react hooks, function components could not implement LocalState, which meant that components with localstate could not be written using function components. This limited the application scope of function components, while react hooks expanded the capabilities of function components. However, during use, you should also pay attention to the following issues, otherwise you will fall into the pit and cause performance loss. Follow the steps below to avoid these traps.

1. Extract variables and methods not related to state changes outside the component function

Every time the state changes, the entire function component is re-executed. As a result, the methods and variables defined inside the function component will be recreated and memory will be reallocated to them, which will affect performance.

import React, {useState,useCallback} from "react";

// Test that the method reallocates memory every time the state changes let testFooMemoAlloc = new Set();

const Page = (props:any) => {
  console.log('Every time the state changes, the function component executes from the beginning')
  const [count, setCount] = useState(0);
  const calc = () => {
    setCount(count + 1);
  }

  const bar = {
    a:1,
    b:2,
    c: 'Variable definitions independent of state'
  }
 
  const doFoo = () => {
    console.log('Methods not related to state');

  }
  testFooMemoAlloc.add(doFoo)
  
  return (
    <>
      <button onClick={calc}>Add 1</button>
      <p>count:{count}</p>
      <p>If testFooMemoAlloc.size increases, it means that memory is reallocated every time: {testFooMemoAlloc.size}</p>
    </>
  )
}

export default Page; 

Variables and methods related to state changes must be placed in the hooks component, while variables and methods unrelated to state can be extracted outside the function component to avoid reallocation of memory every time the state is updated. You can also use useMemo and useCallback to wrap variables and functions respectively to achieve the same effect, which will be discussed later.

import React, {useState,useCallback} from "react";

// Test that the method reallocates memory every time the state changes let testFooMemoAlloc = new Set();

const bar = {
  a:1,
  b:2,
  c: 'Variable definitions independent of state'
}

const doFoo = () => {
  console.log('Methods not related to state');

}

const Page = (props:any) => {
  console.log('Every time the state changes, the function component executes from the beginning')
  const [count, setCount] = useState(0);
  const calc = () => {
    setCount(count + 1);
  }

  testFooMemoAlloc.add(doFoo)
  
  return (
    <>
      <button onClick={calc}>Add 1</button>
      <p>count:{count}</p>
      <p>If testFooMemoAlloc.size increases, it means that memory is reallocated every time: {testFooMemoAlloc.size}</p>
    </>
  )
}

export default Page; 

2. Package subcomponents with memo

The introduction of child components by parent components will cause some unnecessary repeated rendering. Every time the parent component updates the count, the child component will be updated.

import React,{useState} from "react";
const Child = (props:any) => {
    console.log('subcomponent?')
    return(
        <div>I am a child component</div>
    );
}
const Page = (props:any) => {
    const [count, setCount] = useState(0);
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>Add 1</button>
            <p>count:{count}</p>
            <Child />
        </>
    )
}

export default Page; 

Using memo, count changes subcomponents are not updated

import React,{useState,memo} from "react";
const Child = memo((props:any) => {
    console.log('subcomponent?')
    return(
        <div>I am a child component</div>
    );
})
const Page = (props:any) => {
    const [count, setCount] = useState(0);
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>Add 1</button>
            <p>count:{count}</p>
            <Child />
        </>
    )
}

export default Page; 

Passing a second parameter to memo enables deep comparison of objects. When the property value passed by the child component does not change, the child component will not perform meaningless rendering.

memo is not only applicable to function components, but also to class components. It is a high-order component. By default, it only performs shallow comparison on complex objects. If you want to do a deep comparison, you can pass in a second parameter. Unlike shouldComponentUpdate , when deepCompare returns true , render will not be triggered, but if it returns false , it will. And shouldComponentUpdate is just the opposite.

import React, {useState, memo } from "react";
import deepCompare from "./deepCompare";

const Child = memo((props:any) => {
    console.log('subcomponent')
  return (
      <>
      <div>I am a child component</div>
      <div>{ props.fooObj.a}</div>
      </>
    );
}, deepCompare)

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const [fooObj, setFooObj] = useState({ a: 1, b: { c: 2 } })
  console.log('Page rendering starts')
  const calc = () => {
    setCount(count + 1);
    if (count === 3) {
      setFooObj({ b: { c: 2 }, a: count })
    }
  }
  const doBar = () => {
    console.log('Pass the method to the child component to test whether it will cause unnecessary rendering')
  }
    return (
        <>
        <button onClick={calc}>Add 1</button>
        <p>count:{count}</p>
        <Child fooObj={fooObj} doBar={doBar} />
        </>
    )
}

export default Page;
// Deeply compare two objects to see if they are equal export default function deepCompare(prevProps: any, nextProps: any) {
  const len: number = arguments.length;
  let leftChain: any[] = [];
  let rightChain: any = [];
  // // console.log({ arguments });
  //
  if (len < 2) {
    // console.log('You need to pass in two objects to compare the properties of the two objects');
    return true;
  }
  // for (let i = 1; i < len; i++) {
  // leftChain = [];
  // rightChain = [];
  console.log({ prevProps, nextProps });
  if (!compare2Objects(prevProps, nextProps, leftChain, rightChain)) {
    // console.log('The two objects are not equal');
    return false;
  }
  // }
  // console.log('The two objects are equal');

  return true;
}

function compare2Objects(prevProps: any, nextProps: any, leftChain: any, rightChain: any) {
  var p;

  // When both values ​​are NaN, they are not equal in JS, but it is reasonable to consider them equal here if (isNaN(prevProps) && isNaN(nextProps) && typeof prevProps === 'number' && typeof nextProps === 'number') {
    return true;
  }

  // Original value comparison if (prevProps === nextProps) {
    console.log('original value', prevProps, nextProps);
    return true;
  }

  //Construct type comparisonif (
    (typeof prevProps === 'function' && typeof nextProps === 'function') ||
    (prevProps instanceof Date && nextProps instanceof Date) ||
    (prevProps instanceof RegExp && nextProps instanceof RegExp) ||
    (prevProps instanceof String && nextProps instanceof String) ||
    (prevProps instanceof Number && nextProps instanceof Number)
  ) {
    console.log('function', prevProps.toString() === nextProps.toString());
    return prevProps.toString() === nextProps.toString();
  }

  // If the values ​​of the two comparison variables are null and undefined, they will exit here if (!(prevProps instanceof Object && nextProps instanceof Object)) {
    console.log(prevProps, nextProps, 'prevProps instanceof Object && nextProps instanceof Object');
    return false;
  }

  if (prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)) {
    console.log('prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)');
    return false;
  }

  // If the constructors are not equal, the two objects are not equal if (prevProps.constructor !== nextProps.constructor) {
    console.log('prevProps.constructor !== nextProps.constructor');
    return false;
  }

  // If the prototypes are not equal, the two objects are not equal if (prevProps.prototype !== nextProps.prototype) {
    console.log('prevProps.prototype !== nextProps.prototype');
    return false;
  }

  if (leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1) {
    console.log('leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1');
    return false;
  }

  // Traverse the next property object, giving priority to comparing unequal cases for (p in nextProps) {
    if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) {
      console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)');
      return false;
    } else if (typeof nextProps[p] !== typeof prevProps[p]) {
      console.log('typeof nextProps[p] !== typeof prevProps[p]');
      return false;
    }
  }
  // console.log('p in prevProps');
  // Traverse the previous property object, giving priority to comparing unequal cases for (p in prevProps) {
    // Whether a certain property value exists if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) {
      console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)');
      return false;
    }
    // Are the types of property values ​​equal else if (typeof nextProps[p] !== typeof prevProps[p]) {
      console.log('typeof nextProps[p] !== typeof prevProps[p]');
      return false;
    }

    console.log('typeof prevProps[p]', typeof prevProps[p]);
    switch (typeof prevProps[p]) {
      // Object type and function type processing case 'object':
      case 'function':
        leftChain.push(prevProps);
        rightChain.push(nextProps);

        if (!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)) {
          console.log('!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)');
          return false;
        }

        leftChain.pop();
        rightChain.pop();
        break;

      default:
        // Basic type processing if (prevProps[p] !== nextProps[p]) {
          return false;
        }
        break;
    }
  }

  return true;
} 

3. Wrap component methods with useCallback

When the parent component passes a method to the child component, memo seems to have no effect. Whether it is a method defined by const, an arrow function, or a method defined by bind, the child component still executes it.

import React, { useState,memo } from 'react';
//Example of unnecessary rendering of child components interface ChildProps {
  changeName: ()=>void;
}
const FunChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('normal function subcomponent')
  return(
      <>
          <div>I am a normal function subcomponent</div>
          <button onClick={changeName}>Normal function subcomponent button</button>
      </>
  );
}
const FunMemo = memo(FunChild);

const ArrowChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('arrow function subcomponent')
  return(
      <>
          <div>I am an arrow function subcomponent</div>
          <button onClick={changeName.bind(null,'test')}>Arrow function subcomponent button</button>
      </>
  );
}
const ArrowMemo = memo(ArrowChild);

const BindChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('Bind function subcomponent')
  return(
      <>
          <div>I am a Bind function subcomponent</div>
          <button onClick={changeName}>Bind function subcomponent button</button>
      </>
  );
}
const BindMemo = memo(BindChild);

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const name = "test";

  const changeName = function() {
    console.log('Test the method passed to the subcomponent. After using useCallback, will the subcomponent still render invalidly?');
  }

  return (
      <>
          <button onClick={(e) => { setCount(count+1) }}>Add 1</button>
          <p>count:{count}</p>
          <ArrowMemo changeName={()=>changeName()}/>
          <BindMemo changeName={changeName.bind(null)}/>
          <FunMemo changeName={changeName} />
      </>
  )
}

export default Page; 

Use useCallback with the parameter [], after the page is initially rendered, change the value of count, and the subcomponents that pass ordinary functions will no longer render, but the subcomponents that pass arrow functions and methods written in bind will still render

import React, { useState,memo ,useCallback} from 'react';
//Example of unnecessary rendering of child components interface ChildProps {
  changeName: ()=>void;
}
const FunChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('normal function subcomponent')
  return(
      <>
          <div>I am a normal function subcomponent</div>
          <button onClick={changeName}>Normal function subcomponent button</button>
      </>
  );
}
const FunMemo = memo(FunChild);

const ArrowChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('arrow function subcomponent')
  return(
      <>
          <div>I am an arrow function subcomponent</div>
          <button onClick={changeName.bind(null,'test')}>Arrow function subcomponent button</button>
      </>
  );
}
const ArrowMemo = memo(ArrowChild);

const BindChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('Bind function subcomponent')
  return(
      <>
          <div>I am a Bind function subcomponent</div>
          <button onClick={changeName}>Bind function subcomponent button</button>
      </>
  );
}
const BindMemo = memo(BindChild);

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const name = "test";

  const changeName = useCallback(() => {
    console.log('Test the method passed to the subcomponent. After using useCallback, will the subcomponent still render invalidly?');
  },[])

  return (
      <>
          <button onClick={(e) => { setCount(count+1) }}>Add 1</button>
          <p>count:{count}</p>
          <ArrowMemo changeName={()=>changeName()}/>
          <BindMemo changeName={changeName.bind(null)}/>
          <FunMemo changeName={changeName} />
      </>
  )
}

export default Page; 

4. Use useMemo to wrap object variables in components

When the child component uses memo and useCallback, an object property is passed to the child component. If the object value and method have not changed, the child component will be re-rendered regardless of the state change of the parent component.

import React, { useState,memo ,useCallback} from 'react';
//Example of unnecessary rendering of child components - passing an object property value to the child component when using memo and useCallback interface ChildProps {
  childStyle: { color: string; fontSize: string;};
  changeName: ()=>void;
}
const FunChild = ({ childStyle,changeName}: ChildProps): JSX.Element => {
  console.log('normal function subcomponent')
  return(
      <>
          <div style={childStyle}>I am a normal function child component</div>
          <button onClick={changeName}>Normal function subcomponent button</button>
      </>
  );
}
const FunMemo = memo(FunChild);

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const childStyle = {color:'green',fontSize:'16px'};

  const changeName = useCallback(() => {
    console.log('Test the method passed to the subcomponent. After using useCallback, will the subcomponent still render invalidly?');
  },[])

  return (
      <>
          <button onClick={(e) => { setCount(count+1) }}>Add 1</button>
          <p>count:{count}</p>
          <FunMemo childStyle={childStyle} changeName={changeName} />
      </>
  )
}

export default Page; 

Using useMemo can solve the problem of unnecessary updates when passing object properties to child components.

import React, { useState,memo, useMemo, useCallback} from 'react';
//Example of unnecessary rendering of child components interface ChildProps {
  childStyle: { color: string; fontSize: string;};
  changeName: ()=>void;
}
const FunChild = ({ childStyle,changeName}: ChildProps): JSX.Element => {
  console.log('normal function subcomponent')
  return(
      <>
          <div style={childStyle}>I am a normal function child component</div>
          <button onClick={changeName}>Normal function subcomponent button</button>
      </>
  );
}
const FunMemo = memo(FunChild);

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");
  const childStyle = {color:'green',fontSize:'16px'};

  const changeName = useCallback(() => {
    setName('Change the name')
  }, [])
  const childStyleMemo = useMemo(() => {
    return {
      color: name === 'Change the name' ? 'red':'green',
      fontSize: '16px'
    }
  }, [name])

  return (
      <>
          <button onClick={(e) => { setCount(count+1) }}>Add 1</button>
          <p>count:{count}</p>
          <FunMemo childStyle={childStyleMemo} changeName={changeName} />
      </>
  )
}

export default Page; 

The above is the detailed content of the React Hooks use avoidance guide. For more information on the use of React Hooks, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • Detailed explanation of how to pass values ​​between react hooks components (using ts)
  • ReactHooks batch update state and get route parameters example analysis
  • React Hooks Detailed Explanation
  • 30 minutes to give you a comprehensive understanding of React Hooks
  • How React Hooks Work
  • Common usage of hook in react
  • Introduction to 10 Hooks in React

<<:  Explanation of MySQL index types Normal, Unique and Full Text

>>:  Win10 + Ubuntu 16.04 dual system perfect installation tutorial [detailed]

Recommend

js to achieve simple drag effect

This article shares the specific code of js to ac...

Two ways to install Python3 on Linux servers

First method Alibaba Cloud and Baidu Cloud server...

Use xshell to connect to the Linux server

Benefits of using xshell to connect to Linux We c...

Docker starts in Exited state

After docker run, the status is always Exited Sol...

Analysis of MySQL's method of exporting to Excel

This article describes how to use MySQL to export...

Example of how to install kong gateway in docker

1. Create a Docker network docker network create ...

How to use JavaScript and CSS correctly in XHTML documents

In more and more websites, the use of XHTML is rep...

Vue+Vant implements the top search bar

This article example shares the specific code of ...

TCP performance tuning implementation principle and process analysis

Three-way handshake phase Number of retries for c...

Detailed explanation of html-webpack-plugin usage

Recently, I used html-webapck-plugin plug-in for ...

A simple method to be compatible with IE6's min-width and min-height

If a website is widescreen, you drag the browser ...

jQuery realizes the effect of theater seat selection and reservation

jQuery realizes the effect of theater seat select...

Summary of HTML knowledge points for the front end (recommended)

1. HTML Overview htyper text markup language Hype...

Steps to enable TLS in Docker for secure configuration

Preface I had previously enabled Docker's 237...