Search code examples
reactjstypescriptantd

Antd Resizable table columns in TypeScript


I'm implementing antd <Table> component and need to make columns resizable with react-resizable. I was following the example in the documentation, but since my project in in TypeScript and using functional components I had to refactor it and I've faced issues with typings. When I'm trying to implement that onHeaderCell from example:

const initialColumns: ColumnProps<Row>[] = myData.columns.map((column, index) => {
  return {
    ...
    onHeaderCell: (c: ColumnType<Row>) => ({
          width: c.width,
          onResize: handleResize(index),
        }),
    ...
  })

I'm getting this error:

... The types returned by 'onHeaderCell(...)' are incompatible between these types. Type '{ width: string | number | undefined; onResize: (e: SyntheticEvent<Element, Event>, resizeData: ResizeCallbackData) => void; }' has no properties in common with type 'HTMLAttributes'.

My handleResize looks like this:

const handleResize = (index: number) => (e: React.SyntheticEvent<Element, Event>, resizeData: ResizeCallbackData) => {
    const { size } = resizeData
    updateColumns((cols) => {
      const nextColumns = [...cols]
      nextColumns[index] = {
        ...nextColumns[index],
        width: size.width,
      }
      return nextColumns
    })
  }

I've moved ResizableTitle into separate component and <Table /> is implemented this way

const components = {
    header: {
      cell: ResizableTitle,
    },
  }

  return (
    <Table
      bordered
      components={components}
      columns={columns} // columns come from the useState<ColumnProps<Row>[]>()
      ...
    />
  )

How can I change this to get desired results?


Solution

  • So I found a solution without using react-resizable

    const [resizingColumnIndex, _setResizingColumnIndex] = useState<number>();
    const resizingColumnIndexRef = useRef(resizingColumnIndex);
      
    const setResizingColumnIndex = (index: number | undefined) => {
        resizingColumnIndexRef.current = index;
        _setResizingColumnIndex(index);
    };
    
    const resizeListener = (e: MouseEvent) => {
      if (resizingColumnIndexRef.current) {
        handleResize(resizingColumnIndexRef.current, e.movementX);
      }
      window.onmouseup = (e: MouseEvent) => {
        window.removeEventListener("mousemove", resizeListener);
        setResizingColumnIndex(undefined);
      };
    };
    

    In each column title I'm adding

    <div
      className="resizeHandle"
      onMouseDown={() => {
        setResizingColumnIndex(index);
        window.addEventListener("mousemove", resizeListener, { passive: true });
      }}
    />
    

    having style of

      .resizeHandle {
        position: absolute;
        width: 10px;
        height: 100%;
        bottom: 0;
        right: -5px;
        cursor: col-resize;
        z-index: 5;
      }
    

    Finally the handleResize function that updates columns kept in state of the component

      const handleResize = (index: number, delta: number) => {
        updateColumns(prevColumns => {
          const indexNewWidth = Number(prevColumns[index].width) + delta;
          const nextWidth = indexNewWidth >= MIN_COLUMN_WIDTH ? indexNewWidth : MIN_COLUMN_WIDTH;
          return prevColumns.map((col, i) => (i === index ? { ...col, width: nextWidth } : col));
        });
        setTableWidth(prevWidth => prevWidth + delta);
      };