preface

React Hook is now used by many developers, and the advanced custom React Hook based on business refining can save a lot of development time. Therefore, this article introduces two of the most popular custom Hook libraries in the current open source front-end environment:

  • React-use: the earliest and most comprehensive custom hook library abroad, in which hooks almost cover the basic development needs.
  • ahooks: Ali open source library, formerly isumi hooks, andreact-useThe difference is,ahookIt focuses on making hooks that deal with common and complex requirements.

Next, I will introduce the two libraries in turn and list the functions and uses of some of the most powerful hooks in them. I believe that learning these hooks will bring readers a great improvement in development efficiency.

react-use

useMedia

This hook is used to invoke CSS Media syntax for Media queries and return the query result as state, as shown in the official example below:

import { useMedia } from "react-use";

const Demo = () = > {
  // useMedia(query: string, defaultState: boolean = false): boolean;
  const isWide = useMedia("(min-width: 480px)");

  return <div>Screen is wide: {isWide ? "Yes" : "No"}</div>;
};
Copy the code

Through this hook, we can use CSS media syntax to query media device information.

UseMedia is internally implemented through window.matchmedia (). If you are interested, follow the link.

useAudio

A hook for creating and managing HTML

import { useAudio } from "react-use";

const Demo = () = > {
  const [
    // audio: jsx.element whose type is audio
    audio,
    // state: indicates the audio state, with the following values:
    [{"start": [{"start": [{"start": [{"start": [{"start": [{"start": [{"start": 0, "end": 425.952625}], // pause "paused": False, // Current muted" false ", // volume" volume": 1, // A pause or delay due to lack of data ended, and a playback ready to begin. "playing": true } */
    state,
    /** Controls is an AudioControls object that provides a set of internal controls to control audio, such as interface AudioControls {// Play play: () => Promise
      
        | void; // Stop pause: () => void; // mute: () => void; // Unmute: () => void; // Set sound volume: (volume: number) => void; // seek: (time: number) => void; } * /
      
    controls,
    // ref is the object created for useRef to store the audio instance
    ref,
  ] = useAudio(
    // Set the attributes of the native HTML Audio element,
    / / through https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/audio#%E5%B1%9E%E6%80%A7
    // View all settable properties
    {
      src: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3".autoPlay: true});return (
    <div>{audio} {/* When useAudio is called, it does not have the control property set on the parameter, so it does not have the control bar. So we can customize the play control bar */} in the following code<pre>{JSON.stringify(state, null, 2)}</pre>
      <button onClick={controls.pause}>Pause</button>
      <button onClick={controls.play}>Play</button>
      <br />
      <button onClick={controls.mute}>Mute</button>
      <button onClick={controls.unmute}>Un-mute</button>
      <br />
      <button onClick={()= >Controls. The volume (0.1)} > volume: 10%</button>
      <button onClick={()= >Controls. The volume (0.5)} > volume: 50%</button>
      <button onClick={()= > controls.volume(1)}>Volume: 100%</button>
      <br />
      <button onClick={()= > controls.seek(state.time - 5)}>-5 sec</button>
      <button onClick={()= > controls.seek(state.time + 5)}>+5 sec</button>
    </div>
  );
};
Copy the code

React-use also provides useVideo for videos. If you are interested, click on useVideo.

useCss

This hook is used to dynamically create CSS classes based on CSS rules, as shown in the official example below:

import { useCss } from "react-use";

const Demo = () = > {
  // The className returned can be used directly as the className of the element
  // className changes only when CSS rules change
  And unlike inline style, it can also set media queries (@media) and pseudo-selectors and pseudo-classes
  const className = useCss({
    color: "red".border: "1px solid red"."&:hover": {
      color: "blue",}});return <div className={className}>Hover me!</div>;
};
Copy the code

This way you can avoid having to create a separate CSS file to write short CSS styles for your components.

useCopyToClipboard

This hook is used to manage copy and paste.

const Demo = () = > {
  const [text, setText] = React.useState("");
  const[{// The value to be copied, otherwise undefined
      value,
      // An error occurred while copying
      error,
    },
    // The method used for copying, passing in the value to be copied
    copyToClipboard,
  ] = useCopyToClipboard();

  return (
    <div>
      <input value={text} onChange={(e)= > setText(e.target.value)} />
      <button type="button" onClick={()= > copyToClipboard(text)}>
        copy text
      </button>
      {state.error ? (
        <p>Unable to copy value: {state.error.message}</p>
      ) : (
        state.value && <p>Copied {state.value}</p>
      )}
    </div>
  );
};
Copy the code

The dynamic effect is as follows:

CopyToClipboard will report an error if the parameter is undefined or an empty string.

useSet

This hook is used to manage variables of type Set. When we operate on data of type Set in the component, we do not change its pointing. React does a shallow comparison of variables before rendering, and skips rendering if it finds consistency. This prevents the page from updating as the data is updated. The hook is used to handle this, as shown in the following code:

import React from "react";
import { useSet } from "react-use";

const Demo = () = > {
  const [
    set,
    {
      // Return the Set method, such as has, remove, toggle, reset
      add,
    },
  ] = useSet(new Set(["hello"]));
  const [text, setText] = React.useState("");

  return (
    <div>
      <input value={text} onChange={(e)= > setText(e.target.value)} />
      <button type="button" onClick={()= > add(text)}>
        add
      </button>
      <ul>
        {Array.from(set).map((item) => (
          <li>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default Demo;
Copy the code

The code looks like this:

In addition, react-use provides similar hooks for managing other advanced data types, such as:

  • UseList: Manages arrays

  • UseQueue: Manages FIFO queues

  • The Map useMap: management

ahook

useRequest

UseRequest is a powerful asynchronous data management Hooks designed to meet the needs of many scenarios. Let’s take a look at the most basic functionality with an example:

import { useRequest } from "ahooks";
import React, { useState } from "react";

function getUsername() {
  // We use a public open interface to get randomly generated user information
  return fetch("https://randomuser.me/api/").then((res) = > res.json());
}

export default() = > {// This variable name user stores the requested user information
  const [name, setName] = useState({
    first: "".last: ""});// run: used for manual calls to make asynchronous requests,
  // Loading: Indicates the loading state, which is true and false before and after the invocation
  Other variables, such as cancel, are returned to cancel the request
  const { run, loading } = useRequest(getUsername, {
    // If this variable is true, the manually triggered function run will be generated. If it is false, it will be executed by default
    manual: true.// A callback after the run request succeeds
    onSuccess: (result) = > {
      setName(result.results[0].name);
    },
    // In addition to onSuccess, you can also set the following lifecycle configuration items to do some processing at different stages of asynchronous functions.
    // onBefore: triggered before the request
    // onError: The request fails
    // onFinally: Request completion trigger
  });

  if (loading) {
    return <div>loading...</div>;
  }
  return (
    <>
      <button onClick={run}>Loading User Information</button>
      <div>
        {name.first}.{name.last}
      </div>
    </>
  );
};
Copy the code

The dynamic effect is as follows:

In addition to the basic usage above, useRequest allows you to set up polling, anti-shake, throttling, error retry, caching &SWR. See useRequest for details

useAntdTable

Our report page is usually arranged by Antd ProTable:

Among them, if Antd is taken as a public component, the advanced filter bar is realized by Form component, and the title bar, Table area and paging bar are realized by Table component. But every time we write a page like this, we have to repeat some tedious but indispensable logic (called interactive code in this article) to deal with the Form component, the Table component, and the association between the two, for example

Table components:

  1. Paging variable to be defined each time: current page numbercurrentAnd the number of items per pagepageSizeMaybe even a few per pagepageSizeEtc.
  2. To define theloadingTo handle the animation rendered at load time

Table and Form:

  1. You need to definequeryVariable to store the filter parameters so that the request can be automatically re – requested (automatically refreshed after adding, deleting, or modifying)
  2. FormThe current page is reset when the filter parameter changes and the request is madecurrentGo to page one.

With useAntdTable we can dispense with this logic and use a scenario as an example to illustrate its convenience:

Query antD issues in Github according to conditions and put them in Table. The result is as follows:

The label column in the advanced filter bar will immediately send a request when it changes, and it has a reset button. Using useAntdTable saves the need to write a lot of familiar interactive code, as shown in the following example:

import "antd/dist/antd.css";
import { Form, Table, Select, Button } from "antd";
import { useAntdTable } from "ahooks";
import { stringify } from "qs";

export default function App() {
  // Generate the form instance
  const [form] = Form.useForm();
  // Request method, note that the parameters passed in are formatted
  const getTableData = ({ current, pageSize }, formData) = > {
    const { state, labels } = formData;
    const query = stringify({
      state,
      labels,
      page: current,
      per_page: pageSize,
    });

    return fetch(
      // Use github's method to open the interface
      `https://api.github.com/repos/ant-design/ant-design/issues?${query}`
    )
      .then((res) = > res.json())
      .then((res) = > ({
        // This interface can not get the total number of issues, so I just make it up, don't mind
        total: 1000.list: res,
      }));
  };
  // Add getTableData and form to useAntdTable
  // Eventually return the tableProps and Search fields, which are used to manage tables and forms, respectively
  // Where, tableProps is used for Props injected into the Table component
  // Search is used to manage invocation requests
  const { tableProps, search } = useAntdTable(
    getTableData,
    // You can also set some related default parameters, such as:
    // defaultPageSize: The default number of pages
    // refreshDeps: The form data state changes, resetting current to the first page and re-initiating the request.
    // defaultParams: The default data state of the form
    {
      defaultPageSize: 5,
      form,
    }
  );
  // submit is used to submit a form, which is equivalent to collecting form data and page data to request an interface
  // reset is used to reset the current form and invoke the request interface after reset
  const { submit, reset } = search;

  return (
    <div style={{ padding: 12}} >{/* Advanced filter */}<Form form={form} layout="inline" style={{ paddingBottom: 12}} >
        <Form.Item name="labels" label="Label">
          <Select allowClear style={{ width: 150 }} onChange={submit}>
            <Select.Option value="🐛 Bug">🐛 Bug</Select.Option>
            <Select.Option value="help wanted">help wanted</Select.Option>
          </Select>
        </Form.Item>
        <Form.Item name="state" label="State">
          <Select allowClear style={{ width: 150}} >
            <Select.Option value="closed">closed</Select.Option>
            <Select.Option value="open">open</Select.Option>
          </Select>
        </Form.Item>
        <Form.Item>
          <Button type="primary" onClick={submit}>search</Button>
          <Button style={{ marginLeft: 8 }} onClick={reset}>reset</Button>
        </Form.Item>
      </Form>{/* Table area and page bar */}<Table rowKey="id" {. tableProps} >
        <Table.Column dataIndex="title" title="Title" />
        <Table.Column dataIndex="events_url" title="Address" />
        <Table.Column
          dataIndex="labels"
          title="Label"
          width="140px"
          render={(value)= > (
            <div>
              {value.map(({ name }) => (
                <div>{name}</div>
              ))}
            </div>)} / ></Table>
    </div>
  );
}
Copy the code

As you can see, the page code written in conjunction with useAntdTable will allow us to write much less interactive code. I put the above code examples in the Code Sandbox for fun. For more information about useAntdTable, see useAntdTable.

useInViewPort

The hook is used to determine whether an element is within the range of availability. The hook is implemented internally based on the Intersection Observer API.

const [
  / / to a Boolean data type | undefined, on behalf of the element is visible
  inViewport,
  / / data types for the number | undefined, represent the current ratio
  ratio
  ] = useInViewport(
  // THE DOM node, or ref, represents the target to listen fortarget, options? : Options );interface Options{
  // Corresponding to ratio, ratio is updated when the target element and root element cross threshold, and the component function rerendersThreshold:number | number[]
  // Specify the root element to check the visibility of the target. Must be the parent of the target element, default to the browser window if not specified or null.
  root: Element | Document | () = > (Element/Document) | MutableRefObject<Element>
  // Margin of the root elementRootMargin:string;
}
Copy the code

UseInViewport is more practical in the scenario of lazy loading. For example, we use this hook to carry out secondary encapsulation of Image component of ANTD so that it can realize lazy loading. The code is as follows:

import { Image } from "antd";
import React from "react";
import { useInViewport } from "ahooks";
import {LoadingOutlined} from '@ant-design/icons'
type Props = Partial<ImageProps>;

const LazyImage: React.FC<Props> = (props) = > {
  const { src: extraSrc, ... restProps } = props;// Use "wait" as the initial SRC value. The first load will fail, but never mind
  const [src, setSrc] = React.useState("wait");
  const ref = React.useRef();
  const [inViewport] = useInViewport(ref);

  React.useEffect(() = > {
    // If inViewport is true and SRC is the initial value, replace it with the real image path (props. SRC)
    if (inViewport && src === "wait") {
      setSrc(extraSrc);
    }
  }, [inViewport, src, extraSrc]);

  return (
    <div ref={ref}>
      <Image
        {. restProps}
        src={src}{/ *placeholderSet the loading pattern */}placeholder={
          <div style={{height:'100% ',display:'flex',justifyContent:'center',alignItems:'center'}} >
            <LoadingOutlined />
          </div>} / ></div>
  );
};

export default LazyImage;
Copy the code

The components look like this:

I put the sample code above in the Code Sandbox

useNetwork

This hook is used to read the network connection state, I directly use the official website code example to explain:

import React from "react";
import { useNetwork } from "ahooks";

export default() = > {const networkState = useNetwork();

  return (
    <div>
      <div>Network information: </div>
      <pre>{JSON.stringify(networkState, null, 2)}</pre>
    </div>
  );
};

// The page will display the following content
/** Network information: {"online": true, // Whether the Network is online" RTT ": 50, // The estimated round-trip delay under the current connection "saveData": False, // Whether the user agent has set the option to reduce data usage. "downlink": 8.75, // Effective bandwidth estimation (mbit/s) "effectiveType": "4g" // Network connection type} **/
Copy the code

The hook is with the aid of the navigator. The connection and the navigator. Connection. Onchange. This is useful in scenarios where webSocket and webRTC are used.

useUrlState

A hook that manages the request parameters on the URL, location.search. This hook is often used in the scenario where location.search changes with query parameters, as follows:

As you can see from the GIF above, when the current page or the number of items per page changes, location.search changes as well. When we manually change the current page number of location.search to 2 and refresh, the Table also displays the parameters of the second page, as shown below:

The implementation code is as follows:

import { Table } from "antd";
import React, { useCallback } from "react";
import { useAntdTable } from "ahooks";
import "antd/dist/antd.css";
import useUrlState from "@ahooksjs/use-url-state";

export default() = > {const [page, setPage] = useUrlState({ current: 1.pageSize: 10 });

  const getTableData = useCallback(
    ({ current, pageSize }) = > {
      // Call setPage before making the request to change the page variable while changing location.search
      setPage({ current, pageSize });
      let query = `page=${current}&size=${pageSize}`;
      return fetch(`https://randomuser.me/api? results=55&${query}`)
        .then((res) = > res.json())
        .then((res) = > ({
          total: res.info.results,
          list: res.results,
        }));
    },
    [setPage]
  );

  const { tableProps } = useAntdTable(getTableData, {
    defaultParams: [{// defaultParams is an array of data type [pagiantion, formData], and the first element is the pagination parameter
        // Note that the values of the attributes in the page may be strings because they were retrieved from the URL
        current: Number(page.current),
        pageSize: Number(page.pageSize),
      },
    ],
  });

  const columns = [
    {
      title: "name".dataIndex: ["name"."last"],}, {title: "email".dataIndex: "email"}, {title: "phone".dataIndex: "phone"}, {title: "gender".dataIndex: "gender",},];return <Table columns={columns} rowKey="email" {. tableProps} / >;
};
Copy the code

Example code addresses are placed in the code Sandbox

Note: This is based on the react-router useLocation & useHistory & useNavigate for query management, so you need to be sure before using this

  1. Your project is using the React-router 5.x or 6.x version to manage routes
  2. @ahooksjs/use-url-state installed independently

useClickAway

This hook is used to listen for click events outside the target element, equivalent to v-clickoutside in VUE. The hook is mainly used to develop components. We use Modal,Drawer,Select all have a mechanism that collapses when you click outside of a component. Using this hook saves a lot of listening logic. For example, I wrote a mini-drawer component that looks like this:

You can see that I open my Drawer by clicking on button, and then clicking on Drawer doesn’t respond, but clicking on the outer mask layer makes the Drawer collapse.

The code for its mini-drawer component is as follows:

import { useClickAway } from "ahooks";
import { useCallback, useEffect, useRef } from "react";
import "./index.less";

interface Props {
  visible: boolean;
  onClose: () = > any;
}

const Drawer: React.FC<Props> = (props) = > {
  const ref = useRef<HTMLDivElement>();
  // This is used to record whether the animation has ended when it is opened. The flag bit is needed for judgment,
  // useClickAway will be called at the same time every time it is opened, so props. OnClose = false
  // The Drawer cannot be ejected
  const transitioned = useRef(false);

  useClickAway(() = > {
    // When object. visible is true and the presentation animation is complete, clicking on an external element collapses the Drawer and switches status to false
    if (props.visible && transitioned.current) {
      props.onClose();
      transitioned.current = false;
    }
  }, ref);

  const setTransitionedTrue = useCallback(() = > {
    if (props.visible) {
      transitioned.current = true;
    }
  }, [props.visible]);

  return (
    <div className={`drawerThe ${props.visible ? "drawer--opened" :""} `} >
      <div
        className={`drawer__maskThe ${props.visible ? "drawer__mask--opened" : ""
        }`}
      ></div>

      <div
        ref={ref}
        onTransitionEnd={setTransitionedTrue}
        className={`drawer__contentThe ${props.visible ? "drawer__content--opened" : ""
        }`}
      >
        content
      </div>
    </div>
  );
};

export default Drawer;
Copy the code

The example I put in the code sandbox.

useResponsive

This hook is used to obtain responsive information. When making responsive pages, we often control the style and position of DOM elements by querying CSS Media. However, in some scenarios, it is more convenient to control DOM elements by using JS. By default, the size of bootstrap is used for response configuration, as shown below:

{
  'xs': 0.'sm': 576.'md': 768.'lg': 992.'xl': 1200,}Copy the code

Let’s use the following code as an example:

import React from "react";
import { useResponsive } from "ahooks";

export default function () {
  // responsives is a variable of the following data type
  /** { 'xs': boolean, 'sm': boolean, 'md': boolean, 'lg': boolean, 'xl': boolean, } */
  const responsive = useResponsive();
  return (
    <>
      <p>Please change the width of the browser window to see the effect: </p>
      {Object.keys(responsive).map((key) => (
        <p key={key}>
          {key} {responsive[key] ? "✔" : "✘"}
        </p>
      ))}
    </>
  );
}
Copy the code

Finally, we manually adjust the width to see the effect of the page, as shown below:

If you want to configure your own responsive breakpoints, you can use configResponsive. See code Sandbox for an example.

Afterword.

If you like, you might as well click a “like” to support it.