[Ant Design Table + React] Table column scaling implementation

Requirement background: It is necessary to realize the column expansion and contraction of the Antd Table component, and the width can be dragged

In the version of Antd 3.x, the demo example of column scaling is reserved:

Scalable columns can be implemented with the help of react-resizable.

# npm install
npm install react-resizable --save

# yarn installation
yarn add react-resizable

Refer to the official Demo to encapsulate a ResizableTable component:

import {<!-- --> Table } from 'antd';
import type {<!-- --> ColumnsType } from 'antd/lib/table';
import {<!-- --> useEffect,useState } from 'react';
import {<!-- --> Resizable } from 'react-resizable';
import styles from './resizableTable.less';

/**
 * Solve the problem of dragging after releasing the mouse
 * Reference idea: When clicking and dragging, use the browser API Selection.removeAllRanges to clear the text that was originally selected by mistake.
 */
const clearSelection = () => {<!-- -->
  if (window.getSelection) {<!-- -->
    const selection = window.getSelection();
    if (selection) {<!-- -->
      if (selection.empty) {<!-- -->
        // Chrome
        selection.empty();
      } else if (selection.removeAllRanges) {<!-- -->
        // Firefox
        selection.removeAllRanges();
      }
    }
  }
   else if (document.selection & amp; & amp; document.selection.empty) {<!-- -->
     //IE
     document.selection.empty();
   }
};

export const ResizableTitle = (props: any) => {<!-- -->
  const {<!-- --> onResize, width, minWidth, maxWidth, ...restProps } = props;

  // Columns without original width do not support scaling; there will be a sudden jump from the adaptive width to the dragging position; you can also add parameters by yourself, such as disableResize
  if (!width) {<!-- -->
    return <th {<!-- -->...restProps} />;
  }

  const minConstraints: [number, number] | undefined = minWidth
    ? [minWidth, -Infinity]
    : undefined;
  const maxConstraints: [number, number] | undefined = maxWidth
    ? [maxWidth, + Infinity]
    : undefined;
  return (
    <Resizable
      width={<!-- -->width}
      height={<!-- -->0} // No need to adjust height, set to 0
      minConstraints={<!-- -->minConstraints}
      maxConstraints={<!-- -->maxConstraints}
      handle={<!-- -->
        <span
          className="react-resizable-handle"
          onClick={<!-- -->(e) => {<!-- -->
            // Stop bubbling
            e.stopPropagation();
          }}
        />
      }
      onResize={<!-- -->onResize}
      draggableOpts={<!-- -->{<!-- -->
        enableUserSelectHack: false,
        onMouseDown: () => {<!-- -->
          // Handle that in Windows Chrome and Edge, you can still drag when you release the mouse
          clearSelection();
        },
      }}
    >
      <th {<!-- -->...restProps} />
    </Resizable>
  );
};

interface DataType {<!-- -->
  name: {<!-- -->
    first: string;
    last: string;
  };
  gender: string;
  email: string;
  login: {<!-- -->
    uuid: string;
  };
}
const columnsData: ColumnsType<DataType> = [
  {<!-- -->
    title: 'Name',
    dataIndex: 'name',
    sorter: true,
    render: (name) => `${<!-- -->name.first} ${<!-- -->name.last}`,
    width: '20%',
  },
  {<!-- -->
    title: 'Gender',
    dataIndex: 'gender',
    filters: [
      {<!-- --> text: 'Male', value: 'male' },
      {<!-- --> text: 'Female', value: 'female' },
    ],
    width: '20%',
  },
  {<!-- -->
    title: 'Email',
    dataIndex: 'email',
  },
];

const ResizableTable = () => {<!-- -->
  const curColumns: ColumnsType<DataType> = columnsData; // It can be passed in through props, here we use constants as an example
  const [column, setColumns] = useState<ColumnsType<any>>([]);
  //Update table columns when dragging
  const handleResize = (index: number) => {<!-- -->
    return (_e: any, {<!-- --> size }: any) => {<!-- -->
      const newCols = [...column];
      newCols[index] = {<!-- -->
        ...newCols[index],
        width: size.width || '100%',
      };
      setColumns(newCols);
    };
  };

  const mergeColumns = column.map((col, index) => ({<!-- -->
    ...col,
    onHeaderCell: (column: any) => ({<!-- -->
      width: column.width  100,
      //Add minWidth, maxWidth to each column as props of ResizableTitle
      minWidth: 50,
      // maxWidth: 1000,
      onResize: handleResize(index),
    }),
  }));

  useEffect(() => {<!-- -->
    console.log('change', curColumns);
    if (curColumns) {<!-- -->
      setColumns(curColumns);
    }
  }, [curColumns]);

  return (
    <div className={<!-- -->styles.resizeTable}>
      <Table
        size="small"
        components={<!-- -->{<!-- -->
          header: {<!-- -->
            cell: ResizableTitle,
          },
        }}
        columns={<!-- -->mergeColumns}
        dataSource={<!-- -->[]}
      />
    </div>
  );
};
export default ResizableTable;


The style resizableTable.less must be introduced:

.resizeTable {<!-- -->
  :global {<!-- -->
    .react-resizable {<!-- -->
      position: relative;
      background-clip: padding-box;
    }

    .react-resizable-handle {<!-- -->
      position: absolute;
      width: 10px;
      height: 100%;
      bottom: 0;
      right: -5px;
      cursor: col-resize;
      background-image: none;
      z-index: 1;
    }

    .ant-table-filter-column,
    .ant-table-column-sorters {<!-- -->
      display: flex;

      /* co1umn from top to bottom */
      align-items: center;

      /* center represents the horizontal direction */
      justify-content: space-around;
      min-width: 70px;
    }

    .ant-table-thead>tr>th .ant-table-column-sorter {<!-- -->
      // margin-top: -21px;
      display: table-cell;
      vertical-align: middle;
    }
  }
}

The width of a column must be left unset and adaptive. Otherwise the effect will be wrong.

But after I use this plug-in, it still doesn’t work well. There are always some bugs. For example, if you drag a column without setting the width, the entire expansion will be deformed; and if there are a lot of columns, the adaptive column effect is not ideal.

All this solution works but not very well.
You can refer to: https://juejin.cn/post/7182423243553734717

Follow-up solutions:

When checking the information, I saw that a big guy has packaged a telescopic hook use-antd-resizable-header, which is convenient and simple to use. Then the project was introduced.
https://github.com/hemengke1997/use-antd-resizable-header

pnpm add @minko-fe/use-antd-resizable-header


Example of introducing encapsulated components:

import {<!-- --> Table } from 'antd';
import {<!-- --> useAntdResizableHeader } from '@minko-fe/use-antd-resizable-header';
import '@minko-fe/use-antd-resizable-header/dist/style.css';

/** Custom function */
import {<!-- --> isLocaleEn } from '@/utils/commont_rely';

/** type class declaration */
import type {<!-- --> IProps } from '..'; // Self-encapsulated table propsType, for reference only

/** Custom style */
import './style.less';

/** ===================================
 * @name: table component of scalable columns
 * Note: At least one column cannot be dragged (width is not set), please keep at least one column adaptive in width.
 *======================================*/

interface ResizableTableProps extends IProps {<!-- -->
  //Special configuration
  defaultWidth?: number; // Set the minimum width of the column that cannot be dragged. Default 120
  minConstraints?: number; // Drag minimum width, default 60
  maxConstraints?: number; // The maximum drag width is 800 by default and can be set to infinite
}

export default function ResizableTable(props: ResizableTableProps) {<!-- -->
  const {<!-- --> title, defaultWidth, minConstraints, maxConstraints } = props;
  const columns = props?.columns || []; // Colums passed by the component
  const {<!-- --> components, resizableColumns, tableWidth } = useAntdResizableHeader({<!-- -->
    columns,
    defaultWidth: defaultWidth || 120,
    minConstraints: minConstraints || 60,
    maxConstraints: maxConstraints || 800,
  });

  return (
    <div className="resizableTable">
      <Table
        title={<!-- -->title}
        size="small"
        dataSource={<!-- -->data} //data passed by the component
        columns={<!-- -->resizableColumns}
        components={<!-- -->components}
        scroll={<!-- -->{<!-- --> x: tableWidth }}
      />
    </div>
  );
}

It is easy to use and has ideal results. It is recommended to use this plug-in.

11.29 Update
Upgrade to v2.9.0, no need to import css files
Note: The name of the dependent package has also changed
pnpm add use-antd-resizable-header