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

preface

I recently encountered a requirement in a project where the customer wanted the column width of the table to be draggable. The technology stack for the project is React + Ant Design. After reviewing the document, I made some simple changes and encapsulation. The following will use functional components to help you, Crab crab Ù©(‘ω’)Ùˆ.

1. Business scenarios

The customer wants the title bar of the table to be able to be dragged to change the width of the entire column, in order to make it easier for others to view the columns with more data, and to increase the interaction effect of the table components.

Second, implementation ideas

The Class component version of the Demo is explicitly presented in the Ant Design documentation, and this example is modified to be a functional component and packaged to improve its reusability. I enclose the official document address portal.

Three, coding

3.1 Installation Dependencies

First we need to install the dependency React-Resizable, which is at the heart of implementing the drag-and-scale feature.

#NPM install
npm install react-resizable --save

#Yarn installation
yarn add react-resizable
Copy the code

3.2 Rewriting case

Next, let’s change the class writing of the example to functional.

// List page
import { useState } from 'react';
import { Table } from 'antd';
import { Resizable } from 'react-resizable';// Core dependencies
import './resizable-title.css';// This introduction does not add a hash value to the class name, so it is only used for replacement styles

const ResizableTitle = ({ onResize, width, ... restProps }) = > {
    if(! width) {return (<th {. restProps} / >)};return (
        <Resizable
            width={width}
            height={0}
            handle={
                <span
                    className="react-resizable-handle"
                    onClick={e= >{ e.stopPropagation() }} /> } onResize={onResize} draggableOpts={{ enableUserSelectHack: False}} > {/* Add inline style here purpose: make title text unselectable */}<th {. restProps} style={{ userSelect: 'none' }} />
        </Resizable>
    );
};

const List = () = > {
    // Table data
    const data = [
        {
            key: 0.date: '2018-02-11'.amount: 120.type: 'income'.note: 'transfer'}, {key: 1.date: '2018-03-11'.amount: 243.type: 'income'.note: 'transfer'}, {key: 2.date: '2018-04-11'.amount: 98.type: 'income'.note: 'transfer',},];/ / column configuration
    const columns = [
        {
            title: 'Date'.dataIndex: 'date'.width: 200}, {title: 'Amount'.dataIndex: 'amount'.width: 100.sorter: (a, b) = > a.amount - b.amount,
        },
        {
            title: 'Type'.dataIndex: 'type'.width: 100}, {title: 'Note'.dataIndex: 'note'.width: 100}, {title: 'Action'.key: 'action'.render: () = > <a>Delete</a>,}];// Create reactive data with useState
    const [cols, setCols] = useState(columns);
    const colsArray = cols.map((col, index) = > {
        return {
            ...col,
            onHeaderCell: column= > ({ width: column.width, onResize: handleResize(index) })
        };
    });

    // todo adjusts the column width
    const handleResize = index= > {
        return (_, { size }) = > {
            consttemp = [...cols]; temp[index] = { ... temp[index],width: size.width };
            setCols(temp);
        };
    };

    return (
        <div>
            <Table
                components={{ header: { cell: ResizableTitle}}}columns={colsArray}
                dataSource={data}
                pagination={false}
            />
        </div>
    );
};

export default List;
Copy the code

Use plain CSS directly here, and place the styles for the control flex columns in a separate file to avoid conflicts with other style code. Other styles on the page can be placed separately in the same directory as index.less and imported as import styles from ‘./index.less’.

/* resizable-title.css */

/* List page */
.react-resizable {
  position: relative;
  background-clip: padding-box;
}

.react-resizable-handle {
  position: absolute;
  right: -5px;
  bottom: 0;
  z-index: 1;
  width: 10px;
  height: 100%;
  cursor: col-resize;
}
Copy the code

It seems to work well, but we need to encapsulate it.

3.3 Package Reuse

To improve reusability, we need to encapsulate the code as a common component. Internally, Ant Design’s Table component is used.

// components/ResizableTable/index.jsx
// Table component can be dragged
import { useState } from 'react';
import { Table } from 'antd';
import { Resizable } from 'react-resizable';
import './index.css';

const ResizableTitle = ({ onResize, width, ... restProps }) = > {
    if(! width) {return (<th {. restProps} / >)};return (
        <Resizable
            width={width}
            height={0}
            handle={
                <span
                    className="react-resizable-handle"
                    onClick={e= > { e.stopPropagation() }}
                />
            }
            onResize={onResize}
            draggableOpts={{ enableUserSelectHack: false }}
        >
            <th {. restProps} style={{ userSelect: 'none' }} />
        </Resizable>
    );
};

const ResizableTable = ({ columns = [], ... props }) = > {
    // * column data
    const [cols, setCols] = useState(columns);
    const colsArray = cols.map((col, index) = > {
        return {
            ...col,
            onHeaderCell: column= > ({ width: column.width, onResize: handleResize(index) })
        };
    });

    // todo adjusts the column width
    const handleResize = index= > {
        return (_, { size }) = > {
            consttemp = [...cols]; temp[index] = { ... temp[index],width: size.width };
            setCols(temp);
        };
    };

    return (
        <Table
            components={{ header: { cell: ResizableTitle}}}columns={colsArray}
            {. props} / >
    );
};

export default ResizableTable;
Copy the code
// List page
import ResizableTable from '@/components/ResizableTable';// Table component can be dragged

const List = () = > {
    // Table data
    const data = [
        // ...
    ];

    / / column configuration
    const columns = [
        // ...
    ];

    return (
        <div>
            <ResizableTable
                columns={columns}
                dataSource={data}
                pagination={false}
            />
        </div>
    );
};

export default List;
Copy the code
/* components/ResizableTable/index.css */
.react-resizable {
  position: relative;
  background-clip: padding-box;
}

.react-resizable-handle {
  position: absolute;
  right: -5px;
  bottom: 0;
  z-index: 1;
  width: 10px;
  height: 100%;
  cursor: col-resize;
}
Copy the code

3.4 Component Optimization

Of course, it’s not enough to just encapsulate the regular Table component, in case you want to use ProTable, the super Table component in ProComponents. After all, in the background management system, the reasonable use of ProTable can save a lot of development time. At the same time, it is also to make the custom component more complete, really enough and easy to use.

// components/ResizableTable/index.jsx
// Table component can be dragged
import { useState } from 'react';
import { Table } from 'antd';
import ProTable from '@ant-design/pro-table';
import { Resizable } from 'react-resizable';
import './index.css';

const ResizableTitle = ({ onResize, width, ... restProps }) = > {
    if(! width) {return (<th {. restProps} / >)};return (
        <Resizable
            width={width}
            height={0}
            handle={
                <span
                    className="react-resizable-handle"
                    onClick={e= > { e.stopPropagation() }}
                />
            }
            onResize={onResize}
            draggableOpts={{ enableUserSelectHack: false }}
        >
            <th {. restProps} style={{ userSelect: 'none' }} />
        </Resizable>
    );
};

export const ResizableTable = ({ columns = [], ... props }) = > {
    // * column data
    const [cols, setCols] = useState(columns);
    const colsArray = cols.map((col, index) = > {
        return {
            ...col,
            onHeaderCell: column= > ({ width: column.width, onResize: handleResize(index) })
        };
    });

    // todo adjusts the column width
    const handleResize = index= > {
        return (_, { size }) = > {
            consttemp = [...cols]; temp[index] = { ... temp[index],width: size.width };
            setCols(temp);
        };
    };

    return (
        <Table
            components={{ header: { cell: ResizableTitle}}}columns={colsArray}
            {. props} / >
    );
};

export const ResizableProTable = ({ columns = [], ... props }) = > {
    // * column data
    const [cols, setCols] = useState(columns);
    const colsArray = cols.map((col, index) = > {
        return {
            ...col,
            onHeaderCell: column= > ({ width: column.width, onResize: handleResize(index) })
        };
    });

    // todo adjusts the column width
    const handleResize = index= > {
        return (_, { size }) = > {
            consttemp = [...cols]; temp[index] = { ... temp[index],width: size.width };
            setCols(temp);
        };
    };

    return (
        <ProTable
            components={{ header: { cell: ResizableTitle}}}columns={colsArray}
            {. props} / >
    );
};

// Exposes normal tables by default
export default ResizableTable;
Copy the code
// List page
// You can use either "import by default" or "import by module"
import { ResizableTable, ResizableProTable } from '@/components/ResizableTable';

const List = () = > {
    // Table data
    const data = [
        // ...
    ];

    / / column configuration
    const columns = [
        // ...
    ];

    return (
        <>
            <div>General table ResizableTable:</div>
            <br />

            <ResizableTable
                columns={columns}
                dataSource={data}
                pagination={false}
            />

            <br />
            <div>Super table ResizableProTable:</div>
            <br />

            <ResizableProTable
                columns={columns}
                dataSource={data}
                pagination={false}
            />
        </>
    );
};

export default List;
Copy the code

conclusion

This time, we solved the problem of dragging and scaling table header rows in React, and also encapsulated and optimized common components. Short step, no thousands of miles; Not small streams, beyond into rivers and seas. In order to be better and stronger, we need to accumulate solutions to problems in order to make progress.