preface

ReactHooks have been around for a while now, and reacters are pretty handy, but I’ve watched my colleagues who write Hooks use them mostly because they are simpler and faster to write than class components, which is why Hooks came out, But the main reason isn’t to make it easy for developers to write, but to encapsulate some of the repetitive logic in React.

Note: Encapsulating some of the repeated logic in React does not encapsulate common methods. Rather, it encapsulates some of the repeated logic within components so that it can be reused. Ahook has a number of hooks that can be used, as well as hooks that can be used with ANTD, but the logic of each project is only one step away from being used. So developers must know how to package specific Hooks for their own business to make development efficient, code elegant, and use react-hooks. Below I’ll show you how to encapsulate specific hooks based on my own system project.

The instance

The above is a classic list page of a project of our company, the specific logic is

  1. Search condition 1, 2, 3, and 4 interact with each other, and search condition 1 affects search condition 2, and search condition 2 affects search condition 3… And so on. Therefore, when the selection value of search condition 1 changes, search condition 2, 3, and 4 need to be cleared and re-selected, and so on…
  2. When the value of a search criteria changes, the table below needs to request again to update the data, that is, the data of the table affected by the search criteria.

There are many list pages of this logic in our system, so I wrote two Hooks to this logic. Get hooks for restrictions between search criteria, and get hooks for linkage between search criteria and table.

Custom hooks

Overall page code

const columns = [
  {
    title: 'name'.dataIndex: 'name'}, {title: 'age'.dataIndex: 'age'}, {title: 'Graduated institution'.dataIndex: 'school'}, {title: 'Employer'.dataIndex: 'work'}, {title: 'home'.dataIndex: 'home'}, {title: 'note'.dataIndex: 'command',},];const dataSource = [
  {
    name: 'Joe'.age: 29.school: 'the north'.work: Alibaba.home: 'Beijing'.command:'暂无'
  },
  {
    name: 'bill'.age: 19.school: ' '.work: ' '.home: 'Beijing'.command:'暂无'
  },
  {
    name: '马武'.age: 88.school: 'the north'.work: Alibaba.home: 'tianjin'.command:'暂无'
  },
  {
    name: 'Daisy'.age: 27.school: 'the north'.work: "Baidu".home: 'harm'.command:'暂无'
  },
  {
    name: 'tidy'.age: 59.school: Wudaokou Vocational and Technical College.work: 'company'.home: 'Beijing'.command:'暂无'
  },
  {
    name: 'the old today'.age: 59.school: 'no'.work: 'company'.home: 'Beijing'.command:'Ollie to'},]const request = (url, param) = > {
  const paramsLength = Object.values(param).filter(Boolean).length
  return new Promise(res= > {
    setTimeout(() = > {
      res({items:dataSource.slice(0,paramsLength),total:dataSource.length})
    },3000)})}const Page = () = > {
  const [selectValue, setSelectValue] = useState({
    one:undefined.two: undefined.three:undefined.four:undefined});const { onSelectChange } = useLimitSelect(selectValue, setSelectValue);

  const { loading, tableProps } = useTable('/api/fetch', selectValue, request);

  useEffect(() = > {
  console.log(selectValue);
  }, Object.values(selectValue))

  return (
    <div className='page-one'>
      <Row  gutter={16}>
        <Col span={6}>
          <div>Search Condition 1</div>
          <Select value={selectValue.one} onChange={onSelectChange('one')} >
            {[1, 2, 3].map((item) => (
              <Option key={item} value={item}>
                {item}
              </Option>
            ))}
          </Select>
        </Col>
        <Col span={6}>
          <div>Search criteria 2</div>
          <Select value={selectValue.two} onChange={onSelectChange('two')} >
            {[4, 5, 6].map((item) => (
              <Option key={item} value={item}>
                {item}
              </Option>
            ))}
          </Select>
        </Col>
        <Col span={6}>
          <div>Search Criteria 3</div>
          <Select value={selectValue.three} onChange={onSelectChange('three')} >
            {[7, 8, 9].map((item) => (
              <Option key={item} value={item}>
                {item}
              </Option>
            ))}
          </Select>
        </Col>
        <Col span={6}>
          <div>Search Criteria 4</div>
          <Select value={selectValue.four} onChange={onSelectChange('four')} >
            {[10, 11, 12].map((item) => (
              <Option key={item} value={item}>
                {item}
              </Option>
            ))}
          </Select>
        </Col>
      </Row>
      <Table
        columns={columns}
        style={{ marginTop: '20px'}}loading={loading}
        {. tableProps} / >
    </div>
  );
};
Copy the code
  1. requestUse Promise and setTimeout to simulate a function for the request interface
  2. selectValueIn, four attributes correspond to four search condition values respectively, and are initialized with null values
  3. useLimitSelectFor custom hooks between Select,useTableCustom hooks for Select and Table linkage
  4. onSelectChangeOnChange event for the Select drop-down menu
  5. tablePropsA series of Props needed for the Table component

useLimitSelect

export const useLimitSelect = (value, setValue) = > {
    const preValue = useRef(value);
    useEffect(() = > {
        const preV = preValue.current;
        const keys = Object.keys(value);
        let change = false;
        const obj = {};
        for (let i = 0; i < keys.length; i++) {
            if (change) {
                obj[keys[i]] = undefined;
            }
            if(preV[keys[i]] ! == value[keys[i]]) { change =true;
            }
        }
        setValue(pre= >({... pre, ... obj })); preValue.current = value; },Object.values(value));
  
    const onSelectChange = useCallback((type) = > (value) = > {
        setValue(pre= > ({ ...pre, [type]: value }));
    }, [])
    return { onSelectChange };
};
Copy the code

SelectValue and setSelectValue are passed in as arguments. The useRef saves the last selectValue. Each time the value changes, the useRef iterates through the parameters in the value and compares them one by one. Reset their value to undefined, save it to an object, and update value after iterating. In this way, as long as the values of the select that need to be linked in sequence are passed in, as soon as one value changes, the subsequent values will be reset. At the same time, if you want to do some other operations, you can use useEffect to listen inside the component to avoid logical coupling.

useFetch

export const useFetch = (url,param,fetcher,options) = > {
    const [loading, setLoading] = useState(false);
    const [data, setData] = useState(undefined);
    const [isError, setIsError] = useState(false);

    const request = useCallback( async () => {
        setLoading(true);
        try {
            const data = await fetcher(url, param)
            options.onSuccess && options.onSuccess(data, url, param);
            unstable_batchedUpdates(() = >{ setData(data); })}catch (error) {
            setIsError(true)
            options.onError && options.onError(error, url, param)
        }
        setLoading(false);
    }, [fetcher,...Object.values(param),url])
    
    useEffect(() = > {
        request();
    }, Object.values(param));
    return { data, loading, isError, request };
}
Copy the code

UseFetch is for unified handling of hooks that request the encapsulation of my project’s Table data interface. Url is the interface of the request, param is the parameter of the request, fetcher is the function of the request (for example, the request function simulated inside the component), and options are the additional processing functions of the request. For example, some operations need to be performed on the success of the request and some operations need to be performed on the failure of the request. Whenever the PARam parameter changes, the table data needs to be requested again.

useTable

export const useTable = (url = ' ',param = {},fetcher,options = {}) = > {
    const { defaultParams = { page:1.pageSize:15 }, onResponse = Response } = options;
    const [query, setQuery] = useState(() = > ({ page: defaultParams.page, pageSize: defaultParams.pageSize }));
    const{ data, loading, isError, request } = useFetch(url, { ... query, ... param }, fetcher, options);console.log(data);
    const onTableChange = useCallback((pagination) = > {
        const { current, pageSize } = pagination;
        setQuery((prev) = > ({ ...prev, current, pageSize }));
    },[])

    useEffect(() = > {
        setQuery(prev= > ({ ...prev, page: 1.pageSize: 15 }));
    }, Object.values(param));

    const refresh = useCallback(() = > {
        setQuery((prev) = > ({ ...prev, current: 1.pageSize: 15 }));
    }, [])
    
    const newData = onResponse ? onResponse(data) : data;
    return {
        tableProps: {
            onChange: onTableChange,
            dataSource: newData.data,
            pagination: {
                total: newData.total, 
                pageSize: query.pageSize,
                current: query.current,
                size: 'small'.position: ['bottomCenter'],}}, loading, isError, request, refresh,}} :Copy the code

UseTable has a little more code, but it’s not complicated. A total of four arguments can be passed:

  1. Url Indicates the URL that requests table data
  2. Param requests parameters for table data
  3. Fetcher A function that requests table data
  4. Options Specifies additional parameters, including modifying default parameters, returning data, and processing data

Since the default table of my system starts from the first page and each page has 15 data, the page number parameters of the table are 1 and 15 by default. OnResponse is a function reserved for unified processing of the requested data, which depends on the logic of your project. Saving the page number information in a state allows logic for the table page number to change, and the search criteria to change, so the data of the first page needs to be requested again. Call useFetch in useTable and pass in the page number information along with the search criteria information. OnResponse does something to the requested data separately, such as field value changes.

The last

The above are just a few of the custom Hooks I summarized for my own project, each of which can be used directly on its own. Encapsulate custom Hooks. Be sure to extract the common logic in your business and handle only one piece of logic per Hook to avoid coupling. Encapsulating custom Hooks requires not only the ability to abstract and extract logic, but also control over the overall business direction of your project. If a custom Hook can only be used in one place, there is no need to encapsulate custom Hooks. More summary, more practice, willing to read this article developer can become a technical bull!