Skip to content

The table dose not support lazy render on columns (horizontal level) #36

Description

@dingchaoyan1983

assuming I have 100 columns data, the 100 columns will rendered at once. can we support this feature of lazy render in column level?

Activity

  1. changed the title [-]The table dose not support lazy render on columns[/-] [+]The table dose not support lazy render on columns (horizontal level)[/+] on May 14, 2019
  2. dingchaoyan1983 commented on May 14, 2019

    @dingchaoyan1983
    Author

    That is because here

        var expandIcon = expandIconRenderer({
          rowData: rowData,
          rowIndex: rowIndex,
          depth: depth,
          onExpand: this._handleExpand
        });
        var cells = columns.map(function (column, columnIndex) {
          return cellRenderer({
            isScrolling: isScrolling,
            columns: columns,
            column: column,
            columnIndex: columnIndex,
            rowData: rowData,
            rowIndex: rowIndex,
            expandIcon: column.key === expandColumnKey && expandIcon
          });
        });
    
        if (rowRenderer) {
          cells = renderElement(rowRenderer, {
            isScrolling: isScrolling,
            cells: cells,
            columns: columns,
            rowData: rowData,
            rowIndex: rowIndex,
            depth: depth
          });
        }
    

    when render a row, we render the all columns
    can we improve this?

  3. nihgwu commented on May 14, 2019

    @nihgwu
    Contributor

    In that case I think you should consider to use a Grid instead of Table, but I think that's a interesting feature we could add in the future

  4. dingchaoyan1983 commented on May 14, 2019

    @dingchaoyan1983
    Author

    which Grid? the Grid in react-windowing?

  5. nihgwu commented on May 14, 2019

    @nihgwu
    Contributor
  6. dingchaoyan1983 commented on May 14, 2019

    @dingchaoyan1983
    Author

    So I can not use react-base-table, I need to implement such as fixed myself, sad

  7. nihgwu commented on May 14, 2019

    @nihgwu
    Contributor

    or you could send a pr to add virtualization support for columns in BaseTable

  8. dingchaoyan1983 commented on May 14, 2019

    @dingchaoyan1983
    Author

    ok, I will try

  9. perepelitca commented on Jan 6, 2020

    @perepelitca

    Hey, @dingchaoyan1983!
    Did you have a chance to take a look columns virtualization?

  10. sgilligan commented on Aug 4, 2020

    @sgilligan

    +1.

    I ported from a 20+ column react-virtualized multigrid (with plenty of custom renderers) to react-base-table, assuming the use of react-window would include column virtualisation as well as row virtualisation. I discover my assumption to be wrong! I now have a noticeable performance drop in scrolling. No complaint here - my fault, I should have done more research - RBT adds some amazing capability on top of react-window, but to have this would make it complete :)

  11. nihgwu commented on Aug 4, 2020

    @nihgwu
    Contributor

    one of my thoughts is that if we have too many columns, it's a Grid not Table, there is no concept of Row actually, only Cell

  12. sgilligan commented on Aug 5, 2020

    @sgilligan

    In my context, I'd still call it table. Each row relates to a singular entity, each column is an attribute of that entity. Fixed headers/columns/sortable (etc) all conceptually make sense. But arguably, perhaps 20+ columns is too many?

  13. nihgwu commented on Aug 5, 2020

    @nihgwu
    Contributor

    Internally we have table with more then 30 columns, but only few custom columns

  14. sgilligan commented on Aug 5, 2020

    @sgilligan

    @nihgwu Within the TableRow render function, is there a way to find the offset at which the row is currently horizontally scrolled to? I'm messing around with a crude column virtualisation that might help with my specifics (by the way, my table is 10,000 rows, 23 columns and performance is sorta ok with Chrome but really bogs down with Firefox on macOS).

  15. nihgwu commented on Aug 5, 2020

    @nihgwu
    Contributor

    @sgilligan you can get offset via onScroll

  16. sgilligan commented on Aug 6, 2020

    @sgilligan

    @nihgwu this is an experiment with trying to lazy render columns. The simple approach is to not render non-visible columns when scrolling vertically, but show them all when scrolling horizontally. In my own context, it really helps with vertical scrolling performance on firefox/macos, but on first horizontal movement the columns remain un-rendered until the scroll finishes. Is this a viable approach?

  17. nihgwu commented on Aug 6, 2020

    @nihgwu
    Contributor

    the reason for the blank cells when scroll horizontally is that BaseTable optimized to avoid unnecessary re-renders, there is no internal state changed(more specific, either data or columns should be changed to trigger re-render

  18. nihgwu commented on Aug 6, 2020

    @nihgwu
    Contributor

    here is a workable demo modified from yours, some notes:

    1. change children directly takes no effect
    2. you can calculate the visible start and end column index and only update the rowRenderer is the two indices changed, and use those two indices instead of scrollLeft to trigger re-render
    3. you can merge the invisible cells into one, perhaps that would improve the performance a bit, the example may help
  19. sgilligan commented on Aug 7, 2020

    @sgilligan

    aha! thanks so much @nihgwu, that gives me plenty to work with. This is much appreciated and thanks for your outstanding engagement.

  20. nihgwu commented on Aug 7, 2020

    @nihgwu
    Contributor

    @sgilligan I updated the demo to address note 3

  21. sgilligan commented on Aug 8, 2020

    @sgilligan

    @nihgwu ahh .. yes I understand now. That's a clear improvement - will use that - thanks again

  22. added
    questionWould be helpful to the others
    and removed
    enhancementNew feature or request
    on Aug 10, 2020
  23. GregoryPotdevin commented on Jul 6, 2022

    @GregoryPotdevin

    here is a workable demo

    1. change children directly takes no effect
    2. you can calculate the visible start and end column index and only update the rowRenderer is the two indices changed, and use those two indices instead of scrollLeft to trigger re-render
    3. you can merge the invisible cells into one, perhaps that would improve the performance a bit, the example may help

    Thanks, we implemented this for our backoffice (some tables have 10k lines and 50+ columns) and the table feels much more responsive now.

    Maybe this code sample could be added to the standard examples on the website ?

  24. conraddamon commented on Feb 2, 2023

    @conraddamon

    I was able to address this in our component by adjusting the list of columns based on the viewport width and the value of scrollLeft. Since we know the widths of all our columns, it was pretty easy to calculate those that are visible, plus one page (viewport width) on either side. Then I added filler elements to the left and right to make the scrolling look and work right.

  25. anhdd-kuro commented on Mar 19, 2023

    @anhdd-kuro

    For anyone who looking for solution now because the lib have changed a lot
    Here's my example code using custom hook & typescript
    this code writing on version 1.13.4 and base on nihgwu's work around

    typescript issue : #407

    /**
     * ? This hook is used to support virtualization on horizontal scrolling because react-base-table does not support it out of the box.
     *
     * ? The idea is to nullify the cells that are not visible in the viewport. This is done by using the `rowRenderer` prop of react-base-table.
     */
    export const useHorizontalVirtualList = ({
      initialScrollLeft = 0,
      tableWidth,
    }: {
      initialScrollLeft?: number;
      tableWidth: number;
    }) => {
      const [scrollLeft, setScrollLeft] = useState(initialScrollLeft);
    
      const onScroll = useCallback<CVirtualListTableOnScroll>(
        (args) => {
          if (args.scrollLeft !== scrollLeft) {
            setScrollLeft(args.scrollLeft);
          }
        },
        [scrollLeft],
      );
    
      const rowRenderer: BaseTableProps["rowRenderer"] = ({ cells, columns, rowIndex }) => {
        // columns type should be array , this code writing on version 1.13.4
        // this check is just for satisfy ts
        if (Array.isArray(columns)) {
          const notFrozenColumns: ColumnShape[] = columns.filter((col) => !col.frozen);
    
          // minus the frozen col right to get actual visible range
          const frozenRightColumnsWidth: number = columns
            .filter((col: ColumnShape) => col.frozen === "right")
            .reduce((acc, col: ColumnShape) => acc + col.width, 0);
    
          const { outside } = getColumnVisibility({
            offset: scrollLeft,
            frozenRightColumnsWidth,
            columns: notFrozenColumns,
            tableWidth,
          });
    
          outside.forEach((colIdx) => {
            const cell = cells[colIdx];
            if (isValidElement(cell)) {
              cells[colIdx] = cloneElement(cell, {}, null);
            }
          });
        }
    
        return cells;
      };
    
      return {
        onScroll,
        rowRenderer,
        scrollLeft,
      };
    };
    
    const getColumnVisibility = ({
      offset,
      columns,
      tableWidth,
      frozenRightColumnsWidth,
    }: {
      offset: number;
      columns: ColumnShape[];
      tableWidth: number;
      frozenRightColumnsWidth: number;
    }) => {
      // build the net offset for each column
      const netOffsets: number[] = [];
    
      let offsetSum = 0;
      const leftBound = offset;
      const rightBound = offset + tableWidth - frozenRightColumnsWidth;
      const outside: number[] = [];
      const inside: number[] = [];
    
      columns.forEach((col) => {
        netOffsets.push(offsetSum);
        offsetSum += col.width;
      });
    
      // which column offsets are outside the left and right bounds?
      netOffsets.forEach((columnOffset, colIdx) => {
        const isNotVisible = columnOffset < leftBound || columnOffset > rightBound;
    
        if (isNotVisible) {
          outside.push(colIdx);
        } else {
          inside.push(colIdx);
        }
      });
    
      return {
        outside,
        inside,
      };
    };

    Usage

    import BaseTable, { AutoResizer } from "react-base-table";
    import type { BaseTableProps, ColumnShape } from "react-base-table";
    
    type BaseRow = {
      id: string;
      parentId?: string | null;
    };
    
    export type CVirtualListTableProps = {
      columns: CVirtualListTableColumnShape[];
      rows: BaseRow[];
      initialScroll?: {
        x?: number;
        y?: number;
      };
    } & Partial<
      Pick<BaseTableProps, "fixed" | "headerRenderer" | "cellRenderer" | "height" | "onScroll">
    >;
    
    export type CVirtualListTableOnScroll = TypeHelpers.NonNullUndefined<BaseTableProps["onScroll"]>;
    
    export const CVirtualListTable = forwardRef<BaseTable, BaseTableProps>(
      function CVirtualListTableForwardedRef(props: CVirtualListTableProps, ref?: Ref<BaseTable>) {
        const {
          columns,
          rows,
          initialScroll,
          height,
          onScroll: onScrollProp,
          ...otherProps
        } = props;
        const tableWidth = useRef(0);
    
        const { onScroll, rowRenderer } = useHorizontalVirtualList({
          initialScrollLeft: initialScroll?.x,
          tableWidth: tableWidth.current,
        });
    
        return (
          <AutoResizer height={height}>
            {({ width, height: autoHeight }) => {
              tableWidth.current = width;
    
              return (
                <BaseTable
                  width={width}
                  columns={columns}
                  data={rows}
                  height={autoHeight}
                  rowRenderer={rowRenderer}
                  onScroll={(args) => {
                    onScrollProp?.(args);
                    onScroll(args);
                  }}
                  {...otherProps}
                />
              );
            }}
          </AutoResizer>
        );
      },
    );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionWould be helpful to the others

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions