This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

As usual, after a feature is complete I initiate a PR, turn off the computer and leave work. The next day, the PR was rejected with the following statement: “Please use custom hooks to extract common logic for several components”.

As a React beginner, I’m a little scared of custom hooks.

After the leader goes to work, discuss with him: “Can we pass PR first, and then extract after we learn to customize Hook later?”

The leader asked me, “What is the function of Hook?” .

I quickly replied, “Let functional components have their own state and life cycle, and use React features without using Class.”

Leader said seriously, “Have you forgotten another important function of Hook, which makes it easier to reuse the general logic of components, avoiding the nesting hell problem caused by using HOC to reuse the general logic of components? Logic reuse is essential, otherwise the project will be filled with a lot of duplicate code. I don’t accept that. Make sure you use custom hooks to extract common logic from several components before submitting PR.”

Since the leader strongly requested, I had to work overtime to learn how to customize Hook.

When we use any framework, JS library, UI library, encounter custom part, are not willing to use it, because to use it, we have to understand its custom rules, which are often trouble.

Will the React custom Hook rules be cumbersome? After spending a week learning about it and using it, it turns out that’s just as well. So in this article TO share the process of my study, welcome to dig friends comment correction, comment prize oh! See the end of the article.

What is a custom Hook

A custom Hook is a function whose name begins with”useOther hooks can be called from within the function.

React a Hook is a function. It is not a constructor or a self-executing function.

So what kind of function do you want to develop? A function consists of the function name, parameters, function interior, and return value. We first analyze each part of the function according to the requirements for custom hooks in the React official documentation.

  • Function names: start with “use”, as in useState. Why start with “use”? This is because there is no way to determine if the function contains calls to its internal hooks without starting with “use”, and React will not automatically check if the custom Hook violates Hook rules.

  • Parameter: There is no special requirement, but it is mentioned on the React website that it can accept the return value of another Hook as a parameter to transfer information between multiple hooks.

  • Inside a function: Other hooks can be called inside a function, but follow two rules:

    • Use hooks only at the top level. Do not call hooks in loops, conditions, or nested functions. React relies on the order of Hook calls to know which states are related to which useState. Have a look at the official website here for explanation.

    • Never call a Hook in a normal JavaScript function. Only call a Hook in a React function. React provides the Hook itself.

  • Return value: There is no limit to what must be returned, of course a function returns undefined by default.

Custom Hook scenarios

As mentioned at the beginning, there is some common logic in several components, which needs to be extracted in the way of custom hooks. This is the scenario of custom hooks. As noted on the React website, “Custom hooks allow component logic to be extracted into reusable functions.”

We need to think about what is generic logic in components. In the Vue project, general logic is considered to be utility class functions, such as deep copy, anti-shock function, throttling function, get link specified parameters, etc., which I extracted into util.js.

But when it comes to business convenience, such as when an interface is requested in a piece of common logic, extraction is often not done. For example, in the Vue project, this general logic is extracted into mixins.

However, in React, we extract the business-related general logic into a custom Hook. Hook is also a function, and functional components are also functions. It is easier to call functions from functions than mixins. Of course, the common logic of the utility class can also be extracted into a custom Hook.

As the React website says: “When we want to share logic between two functions, we extract it into a third function. Components and hooks are functions, so it works just as well.”

There are many kinds of common logic. A custom Hook is a function that must be defined according to the single responsibility principle. For example, the scenarios for general logic processing are further divided into UI interactions, side effects, life cycles, data processing, DOM processing, optimization processing, and so on.

Customize a simple Hook

Const Demo () = = > {useEffect (() = > {the console. The log (' first rendering components')}, []); } export default Demo;Copy the code

This is definitely the most common logic in a component. The second argument to useEffect receives an empty array indicating that the method passed in as the first argument to useEffect is executed only when the component is first rendered. Use a custom Hook named useMount to extract the general logic as follows:

import { useEffect } from 'react'; const useMount = (fn: () => void) => { useEffect(() => { fn(); } []); }; export default useMount;Copy the code

UseMount This custom Hook receiver function fn is used as an argument to execute the function fn when the component is first rendered.

How to use custom hooks

A custom Hook is defined in a js or ts file and exported with export.

import useMount from '@/hooks/useMount'; Const Demo = () =>{useMount(() =>{console.log(' component first render ')}) return(<div> Demo </div>)} export default Demo;Copy the code

The inside of a custom Hook

React provides 10 internal hooks. Custom hooks are generally assembled and extended by using these 10 internal hooks to define hooks of various functions.

Beginners often have a problem with this, such as using useState to define an A variable in a custom Hook useMyState.

import { useState } from 'react';

const useMyState = () => {
 const [a,setA] = useState();
 return [a,setA]
};

export default useMyState;
Copy the code

We then used useState to define an A variable in the components using useMy to see if it would cause conflicts.

import useMyState from '@/hooks/useMount';

const Demo = () =>{
  const [a,setA] = useState();
  const [b,setB] = useMyState();
  return(
    <div>demo</div>
  )
}
export default Demo;
Copy the code

Don’t worry about this at all. The hooks are completely isolated from each other. You can use useState and useEffect multiple times within a component, and they are completely independent. And a Hook is also a function, and there is a function scope at the bottom.

Now that the question is cleared, how to customize a new Hook with 10 internal hooks. To take a very simple example, we want a value to change during development and the previous value to be displayed. Here’s how it works:

const Demo = () =>{ const [current , setCurrent ] = useState(1); const [previous , setPrevious ]= useState(0); const updata = () => { setCurrent(value =>{ setPrevious(value); return value+1; }}) return (< div > < div > {current} < / div > < div > {previous} < / div > < div onClick = {updata} > change < / div > < / div >). } export default DemoCopy the code

Two useState variables are used to create the current and previous variables, and when changing current, the previous value of current is assigned to previous. Although it is possible to implement this way, it is a little inelegant. If we need to make some judgment on the previous value of current and then assign it to previous, is it necessary to write a lot of irrelevant things in the setCurrent method of changing current, which violates the single principle?

At this point we can define a custom Hook to handle this business scenario, I call this Hook usePrevious.

import { useRef } from 'react';
const usePrevious = (state, compare) =>{
  const prevRef = useRef();
  const curRef = useRef();

  const needUpdate = typeof compare === 'function' ? compare(curRef.current, state) : true;
  if (needUpdate) {
    prevRef.current = curRef.current;
    curRef.current = state;
  }

  return prevRef.current;
}

export default usePrevious;
Copy the code

UseRef is used to save the old and new values of a value, which is better than useState. The reason is that useRef returns a mutable ref object whose current property is initialized as the passed parameter. The ref object returned remains constant throughout the life of the component.

UseRef does not notify you when the ref object’s contents change. The change. Current property does not cause the component to re-render, which is important, whereas any change to the value created by useState does.

HookusePrevious accepts a data state and a function compare. Compare receives the old and new values of the data and can be evaluated within the function before assigning prevRef.current. HookusePrevious returns prevRef.current.

How to use usePrevious is shown in the following example:

import usePrevious from '@/hooks/usePrevious'; const Demo = () =>{ const [current , setCurrent ] = useState(1); const compare = (oldValue,newValue) =>{ if(oldValue ! == newValue){ return true; } } const previous = usePrevious(current,compare); const updata = () => { setCurrent(value =>{ value = value + 1; return value; }}) return (< div > < div > {current} < / div > < div > {previous} < / div > < div onClick = {updata} > change < / div > < / div >). } export default DemoCopy the code

React internal hooks and custom hooks

React internal hooks are used to create custom hooks.

The answer, of course, is no. Why is that? The return value of usePrevious is updated in real time because usePrevious does not provide a method to update a value like useState.

The function body of a functional component is the render() function in the class component. When the state or props of the component changes, the component is re-rendered. The function of a functional component is re-executed.

Const previous = usePrevious(current,compare); const previous = usePrevious(current,compare); UsePrevious Receives a new current and returns a new previous, thus updating in real time.

Define a Hook useMyCount as follows:

const useMyCount = (state) =>{
  return state + 1;
}

export default useMyCount;
Copy the code

Is this custom Hook ok? Let’s use it.

import useMyCount from '@/hooks/usePrevious'; const Demo = () =>{ const [num , setNum ] = useState(1); const count = useMyCount(num); const updata = () => { setNum(value =>{ value = value + 1; return value; }}) return (< div > < div > {num} < / div > < div > {count} < / div > < div onClick = {updata} > add a < / div > < / div >). } export default DemoCopy the code

The React internal Hook is used in useMyCount, but it doesn’t matter if the React internal Hook is used.

Another example is my custom Hook to get the current timestamp:

const useTime = () =>{
  return new Date().getTime();
}

export default useTime;
Copy the code

Customize a request task list Hook:

import * as Api from '@/api'
const useTask = async () =>{
  const res = await Api.getTask();
  return res;
}

export default useTask;
Copy the code

React’s internal hooks are not used in these custom hooks, so it is not difficult to customize hooks.

Some requirements for custom hooks

A custom Hook is like writing a function. If it uses the React internal Hook, it must follow the rules for using the React internal Hook.

In addition, it must meet some requirements of writing functions, such as the definition of parameters, the return value structure requirements.

Finally, the most important thing to note is that a Hook is only responsible for one thing, that is, following a single principle.

  • Parameter requirements

    • There is no parameter

      Allow Hooks to have no arguments.

      const time = useTime();
      Copy the code
    • Single parameter

      Single parameter is directly entered regardless of whether it is mandatory.

      const a = useA(parame);
      Copy the code
    • This parameter is mandatory

      If the number of mandatory parameters is less than two, enter them horizontally.

      const a = useA(parame1, parame2);
      Copy the code

      If there are more than two, enter them as object.

      const a = useA( {parame1:1, parame2:2, parame3:3 } );
      Copy the code
    • This parameter is optional

      Multiple non-mandatory parameters are entered in the object format.

      const a = useA({parame1? , parame2? , parame3? , parame4? });Copy the code
    • This parameter is mandatory + non-mandatory

      Mandatory parameters are first and non-mandatory parameters are second.

      const a = useA(parame,{parame1? , parame2? , parame3? , parame4? });Copy the code
  • Return value structure requirements

    • There is no output

      Allow Hooks to have no output, as is commonly seen in the lifecycle class Hooks.

      useMount(() => {});
      Copy the code
    • The value type

      The Hooks output has only one value.

      const a = useA();
      Copy the code
    • Type the value setValue

      If the output value is value and setValue, the structure is [value, setValue].

      const [state, setState] = useA(a);
      Copy the code
    • A value of type actions

      Where actions is the method for manipulating data.

      Output values are single value and multiple actions, structured as [value, actions].

      const [value, { actions1, actions2, actions3}] = useA(...) ;Copy the code
    • Values of type

      The output value is of type {… values}

      const {value1, value2, value3} = useA();
      Copy the code
    • Values of type actions

      The output values are multi-value and multi-actions and have the structure {… values, … The actions}.

      const {value1, value2, actions1, actions2} = useA(...) ;Copy the code

Following the above requirements, customized hooks are more convenient to use.

conclusion

In terms of custom Hook, it is not difficult. The difficulty lies in the identification and extraction of general logic in components.

The above is my experience in learning how to customize Hook. If it is helpful to dig friends, please give a thumbs up and support. If you find any mistakes, you are welcome to point them out in the comments.

“Welcome to the discussion in the comments section. The nuggets will be giving away 100 nuggets in the comments section after the diggnation project. See the event article for details.”