There are many table pages in the company project, and many businesses are very similar. CURD operation is inevitable, and the operation logic of this part is very complicated, which causes great inconvenience in page maintenance. Therefore, IT occurred to me to use hooks to encapsulate the table in a wave and remove these repeated logic.

Component State Design (Props + State)

In general, the table needs a params search data. Params data will be different according to different business logic, so we pass it into the component as prop. In addition, we also need the table column data OWnColumns, and the baseProps originally supported by antD-Table component. In terms of data, we need to maintain the remote datasource inside the component and import different query methods queryAction according to different services. In addition, we also need a loading state to display loading animation when requesting data, which has a great impact on user experience. Finally, the props and state of our component are clear

const { owncolumns, queryAction, params, baseProps } = props
Copy the code
const paginationInitial: paginationInitialType = {
    current: 1.pageSize: 10.total: 0,}Copy the code

Finally, we merge these states

const initialState: initialStateType = {
    loading: false.pagination: paginationInitial,
    dataSource: []}Copy the code

Logic design

All the states that need to be maintained in the page need corresponding operations to modify them. The reason why we use useState is that useState is not very fine-grained for different operations. Although we can combine states, we need to know more clearly what our code does for different operations. For example, to trigger an action, use the idea of Redux, so we naturally thought of the useState alternative ——-useReducer.

const reducer = (state: initialStateType, action: actionType) = > {
        const { payload } = action
        switch (action.type) {
            case 'TOGGLE_LOADING':  // Change the loading state
                return { ...state, loading: !state.loading }
            case 'SET_PAGINATION':  // Set paging data
                return { ...state, pagination: payload.pagination }
            case 'SET_DATA_SOURCE': // Set the remote data source
                return { ...state, dataSource: payload.dataSource }
            default:
                return state
        }
    }
const [state, dispatch] = useReducer(reducer, initialState)
Copy the code

At this point, we directly call Dispatch and pass in the same action. Then we can enter reducer for processing and return the new state we want

Encapsulation of interfaces

The data in the page needs to be queried through the queryAction passed in, while the component state is handled through the dispatch of different actions

async function fetchData() {
        dispatch({
            type: 'TOGGLE_LOADING'
        })
        // Paging field name conversion
        const { current: indexfrom, pageSize: counts } = state.pagination
        let res = awaitqueryAction({ indexfrom, counts, ... params }).catch(err= > {
            dispatch({ type: 'TOGGLE_LOADING' })
            return{}})/ / close the loading
        dispatch({
            type: 'TOGGLE_LOADING'
        })
        if (res.result === 200) {
            const { totalcounts, list } = res
            // This side is processed according to the different back-end interface
            dispatch({
                type: 'SET_PAGINATION'.payload: {
                    pagination: { ...state.pagination, total: totalcounts }
                }
            })
            // Backfill the list data
            dispatch({
                type: 'SET_DATA_SOURCE'.payload: {
                    dataSource: list
                }
            })
        }
    }
Copy the code

The next logical step is to use useEffect to add side effects to the component, pull data when the component is mounted or updated, but since the function is defined inside the component, the component regenerates this method every time it is updated. For useEffect, His dependencies are redefined each time the component update changes, which in turn executes the method -> component update -> executes the method…… , thus creating an endless loop. There are two ways to solve this problem.

  1. Pull the function out of the component, passing in the Dispatch function as a parameter
  2. Using the useCallback function optimization, the method is cached, and the fetchData method is executed again only when the callback’s dependency changes.

I won’t talk about the first one here, but here we use the second way to optimize.

const fetchDataWarp = useCallback(
    fetchData,
    [params, state.pagination.current, owncolumns, queryAction],
)
 useEffect((a)= > {
    fetchDataWarp()
}, [fetchDataWarp])
Copy the code

Component event handling

So far we’ve only encapsulated paging, so you only need to maintain page change events

// Change the page numberfunction handleTableChange(payload: any) {
if (payload) {
    const { current } = payload
    dispatch({
        type: 'SET_PAGINATION', payload: { pagination: { ... state.pagination, current } } }) } }Copy the code

render

<Table columns={owncolumns(fetchData)} pagination={state.pagination} dataSource={state.dataSource} loading={state.loading} onChange={handleTableChange} {... baseProps} />Copy the code

TS type

import { Columns } from '.. /.. /types/types'
import { TableProps } from 'antd/lib/table/interface'
interface queryActionType {
    (arg: any): Promise<any>
}
interface ColumnFunc {
    (updateMethod: queryActionType): Array<Columns>
}
exportinterface ArgTableProps { baseProps? : TableProps<any> owncolumns: ColumnFunc queryAction: queryActionType params: any listName? : string }export interface paginationInitialType {
    current: number
    pageSize: number
    total: number
}
export interface initialStateType {
    loading: boolean
    pagination: paginationInitialType
    dataSource: Array<any>
}
export interface actionType {
    type: string payload? : any }Copy the code

Component complete code

import React, { useEffect, useReducer, useCallback } from 'react'
import { Table } from 'antd';

import { ArgTableProps, paginationInitialType, initialStateType, actionType } from './type'

const useAsyncTable: React.FC<ArgTableProps> = props= > {
    const { owncolumns, queryAction, params, baseProps } = props
    // Paging data
    const paginationInitial: paginationInitialType = {
        current: 1.pageSize: 10.total: 0,}// Full data of the table component
    const initialState: initialStateType = {
        loading: false.pagination: paginationInitial,
        dataSource: []}const reducer = (state: initialStateType, action: actionType) = > {
        const { payload } = action
        switch (action.type) {
            case 'TOGGLE_LOADING':
                return { ...state, loading: !state.loading }
            case 'SET_PAGINATION':
                return { ...state, pagination: payload.pagination }
            case 'SET_DATA_SOURCE':
                return { ...state, dataSource: payload.dataSource }
            default:
                return state
        }
    }
    const [state, dispatch] = useReducer(reducer, initialState)

    // Change the page number
    function handleTableChange(payload: any) {
        if (payload) {
            const { current } = payload
            dispatch({
                type: 'SET_PAGINATION'.payload: {
                    pagination: {
                        ...state.pagination,
                        current
                    }
                }
            })
        }
    }
    // useCallback wraps requests, caches dependencies and optimizes component performance
    const fetchDataWarp = useCallback(
        fetchData,
        [params, state.pagination.current, owncolumns, queryAction],
    )
    async function fetchData() {
        dispatch({
            type: 'TOGGLE_LOADING'
        })
        // Paging field name conversion
        const { current: indexfrom, pageSize: counts } = state.pagination
        let res = awaitqueryAction({ indexfrom, counts, ... params }).catch(err= > {
            dispatch({ type: 'TOGGLE_LOADING' })
            return{}})/ / close the loading
        dispatch({
            type: 'TOGGLE_LOADING'
        })
        if (res.result === 200) {
            const { totalcounts, list } = res
            dispatch({
                type: 'SET_PAGINATION'.payload: {
                    pagination: { ...state.pagination, total: totalcounts }
                }
            })
            // Backfill the list data
            dispatch({
                type: 'SET_DATA_SOURCE'.payload: {
                    dataSource: list
                }
            })
        }
    }
    useEffect((a)= > {
        fetchDataWarp()
    }, [fetchDataWarp])
    return (
        <Table
            columns={owncolumns(fetchData)}
            pagination={state.pagination}
            dataSource={state.dataSource}
            loading={state.loading}
            onChange={handleTableChange}
            {. baseProps} / >
    )
}
export default useAsyncTable
Copy the code

README.md

Prop

attribute type The default value note
owncolumns (updatefunc:Function) : columns Will choose parameters Updatefunc is used to refresh the list
queryAction (payload):Promise Will choose parameters Used for list data retrieval
baseProps TableProps from antd You can choose any The basic props of ANTD
params object {} Request additional parameters

Using the example

 const getColumn: getColumnType = updateMethod= > {
    return[{title: "Project Name".dataIndex: "project_name".key: "project_name"}, {title: 'operation'.key: 'setting'.width: 200.render: (text: any, record: any, index: number) = > {
          return (
            <div>
              <Button type="primary" style={{ marginRight: '5px' }}>To view</Button>
              <Popconfirm
                title="This operation will permanently delete the item. Do you want to continue?"
                okText="Sure"
                cancelText="Cancel"
                onConfirm={()= > {
                  updateMethod()
                }}
              >
                <Button type="danger">delete</Button>
              </Popconfirm>
            </div>)}}]; } render(){return (
          <ArgTable
                owncolumns={updatefunc= > getColumn(updatefunc)}
                queryAction={API.http_getProjectList}
                baseProps={{ rowKey: record => record.project_id }}
                params={searchData} 
            />
      )
  }
 
Copy the code