♣ Use of React Hooks

Posted by jaku78 on Fri, 04 Feb 2022 19:07:23 +0100

Use of React Hooks

Why do I need a Hook?

Hook is a new feature of React 16.8 that allows us to use state and other React features (such as lifecycle) without writing class es.

  • The class component can define its own state, which is used to store the state within the component itself. Functional components are not possible because each call to a function produces a new temporary variable.
  • The class component has its own life cycle, in which we can complete our logic. For example, a network request is sent in componentDidMount and the lifecycle function is executed only once; Before learning hooks, if a network request is sent in a function by a functional component, it means that the network request will be re-sent once for each re-rendering.
  • The class component can only re-execute render functions and life cycle functions componentDidUpdate that we want to re-invoke when the state changes. When a functional component is re-rendered, the entire function is executed, and there seems to be nowhere to call it only once;

Problems with Class Components

  • Complex components become difficult to understand:
    • When we first write a class component, the logic is usually simple and not very complex. But as business grows, our class components become more and more complex;
    • For example, in componentDidMount, you might have a lot of logic code: network requests, listening for some events (which also needs to be removed in componentWillUnmount);
    • Such class es are actually very difficult to split because their logic is often mixed up and forced splitting can lead to over-design and code complexity.
  • Ununderstandable class:
    • Many people find that learning the ES6 class is a barrier to learning React.
    • For example, in the class, we have to find out who this is, so it takes a lot of effort to learn this.
    • Although I think front-end developers have to master this, it's still a hassle to deal with;
  • Component reuse status is difficult:
    • Previously, for some state reuse, we needed to use higher-order components or render props;
    • Like the connect or in redux or withRouter in react-router we learned earlier, these higher-order components are designed for state reuse.
    • Or similar to Provider or Consumer to share some states, but when we use Consumer many times, our code is nested a lot;
    • These codes make it very difficult for us to write and design.

Hook can solve these problems mentioned above.

A brief summary of hooks is that it allows us to use state and other React features without writing class es; But we can extend the use of this to solve the problems we mentioned earlier.

Scenarios for using Hook:

  • Hook basically replaces all the places where we used the class components before (except for some very unusual scenarios);
  • But if it's an old project, you don't need to refactor all your code directly to Hooks because it's fully downward compatible and you can use it incrementally;
  • Hook can only be used in function components, not in class components or outside of function components;

useState parsing

useState is from react and needs to be imported from react, it is a hook;

  • Parameter: Initialization value, if not set to undefined;
  • Return value: Array containing two elements;
    • Element one: the value of the current state (the first call is the initialization value);
    • Element 2: A function that sets the state value;
view code
import React, { useState } from 'react'

export default function ComplexHookState() {

  const [friends, setFrineds] = useState(["kobe", "lilei"]);
  const [students, setStudents] = useState([
    { id: 110, name: "why", age: 18 },
    { id: 111, name: "kobe", age: 30 },
    { id: 112, name: "lilei", age: 25 },
  ])

  function addFriend() {
    friends.push("hmm");
    setFrineds(friends);
  }

  function incrementAgeWithIndex(index) {
    const newStudents = [...students];
    newStudents[index].age += 1;
    setStudents(newStudents);
  }

  return (
    <div>
      <h2>Friends List:</h2>
      <ul>
        {
          friends.map((item, index) => {
            return <li key={index}>{item}</li>
          })
        }
      </ul>
      <button onClick={e => setFrineds([...friends, "tom"])}>Add a friend</button>
      {/* Wrong practices */}
      <button onClick={addFriend}>Add a friend</button>

      <h2>Student List</h2>
      <ul>
        {
          students.map((item, index) => {
            return (
              <li key={item.id}>
                <span>Name: {item.name} Age: {item.age}</span>
                <button onClick={e => incrementAgeWithIndex(index)}>age+1</button>
              </li>
            )
          })
        }
      </ul>
    </div>
  )
}

But there are two additional rules for using them:

  • Hook can only be called at the outermost level of a function. Do not call in loops, conditional judgments, or subfunctions.
  • Hook can only be called in React's function components. Do not call in other JavaScript functions.

The API for State Hook is useState

  • UseState helps us define a state variable, and useState is a new method that works with this in the class. State provides exactly the same functionality. In general, variables "disappear" after the function exits, while variables in the state are left behind by React.
  • useState accepts the only parameter that is used as the initialization value when the first component is called. (If no arguments are passed, the initialization value is undefined).
  • useState is an array, and we can deconstruct it to make assignments very convenient. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

Know Effect Hook

Now that we've defined state in functional components through hook s, what about life cycles?

  • Effect Hook lets you do something similar to the life cycle in a class;
  • In fact, some Side Effects of React updating DOM are similar to network requests, updating DOM manually, and listening for some events.
  • So the Hook that accomplishes these functions is called the Effect Hook;

Interpretation of useEffect:

  • With useEffect's Hook, you can tell React that it needs to do something after rendering.
  • useEffect requires us to pass in a callback function that will be called back after React has finished updating the DOM.
  • By default, this callback function is executed either after the first rendering or after each update.

During the writing of class components, some side-effect code needs to be cleaned up in componentWillUnmount: for example, manually calling subscribe in our previous event bus or Redux; Need to have a corresponding unsubscribe on componentWillUnmount; How does Effect Hook simulate componentWillUnmount?

The callback function A passed in by useEffect can itself have a return value, which is another callback function B:type EffectCallback = () => (void | () => void | undefined));

Why return a function in effect? This is an optional cleanup mechanism for effects. Each effect can return a cleanup function. This brings together the logic of adding and removing subscriptions; They are all part of effectect;

view code
import React, { useEffect, useState } from 'react'

export default function EffectHookCancelDemo() {

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

  useEffect(() => {
    console.log("Subscribe to some events");

    return () => {
      console.log("Unsubscribe Event")
    }
  }, []);

  return (
    <div>
      <h2>EffectHookCancelDemo</h2>
      <h2>{count}</h2>
      <button onClick={e => setCount(count + 1)}>+1</button>
    </div>
  )
}

Use multiple effects

Hook allows us to separate them by the purpose of the code, rather than as lifecycle functions do: React calls each effect in the component in turn, in the order in which the effects are declared;

view code
import React, { useState, useEffect } from 'react'

export default function MultiEffectHookDemo() {
  const [count, setCount] = useState(0);
  const [isLogin, setIsLogin] = useState(true);

  useEffect(() => {
    console.log("modify DOM", count);
  }, [count]);

  useEffect(() => {
    console.log("Subscription Events");
  }, []);

  useEffect(() => {
    console.log("Network Request");
  }, []);

  return (
    <div>
      <h2>MultiEffectHookDemo</h2>
      <h2>{count}</h2>
      <button onClick={e => setCount(count + 1)}>+1</button>
      <h2>{isLogin ? "coderwhy": "Not logged in"}</h2>
      <button onClick={e => setIsLogin(!isLogin)}>Sign in/Cancellation</button>
    </div>
  )
}

Effect performance optimization

By default, the callback function of useEffect is re-executed each time it is rendered, but this causes two problems:

  • Some of the code we just want to execute once is similar to what is done in componentDidMount and componentWillUnmount; (such as network requests, subscriptions, and unsubscribes);
  • In addition, multiple executions can cause performance problems.

How do we decide when and when useEffect should not be executed? UseEffect actually has two parameters:

  • Parameter 1: Callback function executed;
  • Parameter 2: The useEffect is not re-executed until which state s have changed; (Who influenced)

However, if a function we don't want to depend on anything, we can also pass in an empty array [], where the two callback functions correspond to the componentDidMount and componentWillUnmount life cycle functions, respectively;

Use of useContext

In previous development, we used shared Context s in our components in two ways:

  • Class components can use class names. contextType = MyContext, get context in class;
  • Multiple Contexts or MyContext in a functional component. Share context as Consumer;

However, there is a lot of nesting in the way multiple Contexts are shared: Context Hook allows us to get the value of a Context directly through Hook;

useReducer

Many people see that the first reaction of useReducer should be an alternative to redux, but it is not.

useReducer is just an alternative to useState:

In some scenarios, if the state's processing logic is complex, we can use useReducer to split it up; Or it can be used when the modified state needs to depend on the previous state.

view code
import React, { useState, useReducer } from 'react';

import reducer from './reducer';

export default function Home() {
  // const [count, setCount] = useState(0);
  const [state, dispatch] = useReducer(reducer, {counter: 0});

  return (
    <div>
      <h2>Home Current Count: {state.counter}</h2>
      <button onClick={e => dispatch({type: "increment"})}>+1</button>
      <button onClick={e => dispatch({type: "decrement"})}>-1</button>
    </div>
  )
}
view code
import React, { useReducer } from 'react';

import reducer from './reducer';

export default function Profile() {
  // const [count, setCount] = useState(0);
  const [state, dispatch] = useReducer(reducer, { counter: 0 });

  return (
    <div>
      <h2>Profile Current Count: {state.counter}</h2>
      <button onClick={e => dispatch({ type: "increment" })}>+1</button>
      <button onClick={e => dispatch({ type: "decrement" })}>-1</button>
    </div>
  )
}
view code
export default function reducer(state, action) {
  switch(action.type) {
    case "increment":
      return {...state, counter: state.counter + 1};
    case "decrement":
      return {...state, counter: state.counter - 1};
    default:
      return state;
  }
}

The data is not shared, they just use the same counterReducer function. So useReducer is just a replacement for useState, not Redux.

useCallback

The actual purpose of useCallback is to optimize performance.

How to optimize performance?

  • useCallback returns the memoized value of a function;
  • With the same dependency, the value returned is the same when defined multiple times;

case

  • Case 1: Use useCallback and do not use useCallback to define whether a function will lead to performance optimization;
  • Case 2: Use useCallback and do not use useCallback to define whether a function passed to a subcomponent will result in performance optimization;

Usually the purpose of using useCallback is not to have subcomponents render more than once, not to cache functions;

view code
import React, { useState, useCallback, useMemo } from 'react'
//Unable to perform performance optimization
export default function CallbackHookDemo01() {
  const [count, setCount] = useState(0);

  const increment1 = () => {
    console.log("implement increment1 function");
    setCount(count + 1);
  }

  const increment2 = useCallback(() => {
    console.log("implement increment2 function");
    setCount(count + 1);
  }, [count]);

  const increment3 = useMemo(() => {
    return () => {
      console.log("implement increment2 function");
      setCount(count + 1);
    }
  }, [count]);

  return (
    <div>
      <h2>CallbackHookDemo01: {count}</h2>
      <button onClick={increment1}>+1</button>
      <button onClick={increment2}>+1</button>
    </div>
  )
}
view code
import React, {useState, useCallback, memo} from 'react';

/**
 * useCallback When to use it?
 * Scenario: useCallback handles a function in a component when passed to a child element for callback.
 */

const HYButton = memo((props) => {
  console.log("HYButton Rendering: " + props.title);
  return <button onClick={props.increment}>HYButton +1</button>
});

export default function CallbackHookDemo02() {
  console.log("CallbackHookDemo02 Rendering");

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

  const increment1 = () => {
    console.log("implement increment1 function");
    setCount(count + 1);
  }

  const increment2 = useCallback(() => {
    console.log("implement increment2 function");
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <h2>CallbackHookDemo01: {count}</h2>
      {/* <button onClick={increment1}>+1</button>
      <button onClick={increment2}>+1</button> */}
      <HYButton title="btn1" increment={increment1}/>
      <HYButton title="btn2" increment={increment2}/>

      <button onClick={e => setShow(!show)}>show switch</button>
    </div>
  )
}

useMemo

The practical purpose of useMemo is also to optimize performance. How to optimize performance?

  • useMemo also returns a memoized value;
  • With the same dependency, the value returned is the same when defined multiple times;

Case:

  • Case 1: Do you have to recalculate every time you render?
  • Case 2: Use useMemo to optimize performance when subcomponents pass objects with the same content
view code
import React, {useState, useMemo} from 'react';

function calcNumber(count) {
  console.log("calcNumber Recalculation");
  let total = 0;
  for (let i = 1; i <= count; i++) {
    total += i;
  }
  return total;
}

export default function MemoHookDemo01() {
  const [count, setCount] = useState(10);
  const [show, setShow] = useState(true);

  // const total = calcNumber(count);
  const total = useMemo(() => {
    return calcNumber(count);
  }, [count]);

  return (
    <div>
      <h2>Calculate the sum of numbers: {total}</h2>
      <button onClick={e => setCount(count + 1)}>+1</button>
      <button onClick={e => setShow(!show)}>show switch</button>
    </div>
  )
}
view code
import React, { useState, memo, useMemo } from 'react';

const HYInfo = memo((props) => {
  console.log("HYInfo Rendering");
  return <h2>Name: {props.info.name} Age: {props.info.age}</h2>
});

export default function MemoHookDemo02() {
  console.log("MemoHookDemo02 Rendering");
  const [show, setShow] = useState(true);

  // const info = { name: "why", age: 18 };
  const info = useMemo(() => {
    return { name: "why", age: 18 };
  }, []);

  return (
    <div>
      <HYInfo info={info} />
      <button onClick={e => setShow(!show)}>show switch</button>
    </div>
  )
}

 

Topics: React