KevinVandy / mantine-react-table

A fully featured Mantine V5 implementation of TanStack React Table V8, forked from Material React Table
https://www.mantine-react-table.com/
MIT License
838 stars 140 forks source link

Column Virtualization - if filtered rows = 1, filters need to be cleared #344

Open dafer660acn opened 4 months ago

dafer660acn commented 4 months ago

mantine-react-table version

v1.3.4

react & react-dom versions

v18.2.0

Describe the bug and the steps to reproduce it

Hello,

I am trying to implement virtualization and using filters (also saving them) as per your manual using local storage as example, so when it re-renders, it's getting the filters from the local Storage, and it's working fine.

However, when data length is only 1, it doesn't display and I must clear the filter to show all of them.

When we have 2 rows, it displays just fine and we can see them both: mantine_bug_001

However, when I delete one, have only one row to present, I need to clear out the filter and re-apply it again: mantine_bug_002

Thanks in advance, Daniel

Minimal, Reproducible Example - (Optional, but Recommended)

This is from the main component, which is imported from a custom component:

Component.jsx

<CustomMantineReactTable
    data={overtimes || []} columns={columns} isError={isError}
    isFetching={isFetching} isLoading={isLoading} refetch={refetch}
    rowVirtualization={true} columnVirtualization={false}
    enableFilter={true} enableSelection={true}
    grouping={grouping} setGrouping={setGrouping}
    sorting={sorting} setSorting={setSorting}
    visibility={visibility} setVisibility={setVisibility}
    order={order} setOrder={setOrder}
    filtering={filtering} setFiltering={setFiltering}
    selection={selection} setSelection={setSelection}
    CustomTopToolbarApplications={renderCustomTopToolbarApplications}
    CustomTopToolbarTools={renderCustomTopToolbarTools}
    CustomTopToolbarFilters={renderCustomTopToolbarFilters}
    CustomToolbarAlertBannerContent={renderToolbarAlertBannerContent}

Then I created the main component to style and customize...

function CustomMantineReactTable({
                                     columns, data, detailPanelBG, error, isError, refetch,
                                     isLoading, isFetching, isExpanded, CustomRowActions, CustomRowActionMenuItems,
                                     CustomBottomToolbar, CustomTopToolbarTools, CustomTopToolbarApplications,
                                     CustomToolbarAlertBannerContent,
                                     CustomTopToolbarFilters, CustomDetailPanel, CustomMantineEditTextInputProps,
                                     CustomMantineEditSelectProps, filtersWidth, noFilterLabel, PageSize,
                                     enableEditing, enableFilter, enablePagination, enableSelection, enableExpand,
                                     rowVirtualization, columnVirtualization,
                                     grouping, setGrouping, sorting, setSorting, selection, setSelection,
                                     visibility, setVisibility, order, setOrder, filtering, setFiltering,
                                 }) {
    // relevant local state:
    const rowVirtualizerInstanceRef = useRef(null)
    const tableInstanceRef = useRef(null)
    const [globalFilter, setGlobalFilter] = useState('')

    useEffect(() => {
        // scroll to the top of the table when the sorting changes
        // we must also verify if the scroll element is not null
        // otherwise it will throw an error.
        if (rowVirtualizerInstanceRef.current?.scrollElement !== null) {
            rowVirtualizerInstanceRef.current?.scrollToIndex(0)
        }
    }, [sorting])

    const containerHeight = (grouping, selection, enablePagination,) => {
            // calc(100vh - 315px) -> grouping, selection and pagination enabled
            // calc(100vh - 245px) -> grouping and selection enabled
            // calc(100vh - 185px) -> normal height

            if (grouping?.length > 0 && Object.keys(selection).length > 0 && enablePagination) {
                return 'calc(100vh - 375px)'
            } else if (Object.keys(selection).length > 0 && enablePagination) {
                return 'calc(100vh - 315px)'
            } else if (grouping?.length > 0 && enablePagination) {
                return 'calc(100vh - 315px)'
            } else if (grouping?.length > 0 && Object.keys(selection).length > 0) {
                return 'calc(100vh - 305px)'
            } else if (enablePagination) {
                return 'calc(100vh - 255px)'
            } else if (Object.keys(selection).length > 0) {
                return 'calc(100vh - 245px)'
            } else if (grouping?.length > 0) {
                return 'calc(100vh - 245px)'
            } else {
                return 'calc(100vh - 185px)'
            }
        }

    return (
        <MantineReactTable
            columns={columns}
            data={data}
            tableInstanceRef={tableInstanceRef}
            initialState={{
                showColumnFilters: enableFilter ? enableFilter : false,
                columnFilters: filtering,
                density: 'xs',
                columnVisibility: visibility,
                columnOrder: order,
                rowSelection: enableSelection ? selection : null,
                pagination: {
                    pageSize: CustomDetailPanel ?
                        (PageSize ? PageSize : 50) :
                        (PageSize ? PageSize : 75),
                    pageIndex: 0
                },
            }}

            // specify the layout mode here
            // layoutMode={!CustomDetailPanel ? 'grid' : 'semantic'}
            layoutMode={'grid'}
            // use memoMode cells which improve performance when using rowVirtualization
            memoMode={'rows'}

            // if we use global filters, declare how you want them to work here
            globalFilterFn={'contains'}
            globalFilterModeOptions={['contains']}

            // override display styles
            displayColumnDefOptions={{
                'mrt-row-actions': {
                    header: 'Actions', //change header text
                    size: 75, //make actions column wider
                },
                'mrt-row-expand': {
                    header: '',
                    size: (grouping?.length > 0 || enableExpand) ? 75 : 0,
                },
                'mrt-row-select': {
                    header: '',
                    size: enableSelection ? 75 : 0,
                    // size: Object.keys(selection).length > 0 ? 75 : 0, //make select column wider
                }
            }}

            // enable or disable functionalities here
            enableStickyHeader={true}
            enableStickyFooter={false}
            enableTableFooter={false}
            enableFullScreenToggle={false}
            enableBottomToolbar={enablePagination}
            enableClickToCopy={false}
            enableFacetedValues={true}
            enableHiding={true}
            enableDensityToggle={false}
            enablePinning={false}
            enableGrouping={true}

            // Editing the table
            enableEditing={enableEditing}
            editDisplayMode={"table"}
            mantineEditTextInputProps={CustomMantineEditTextInputProps}
            mantineEditSelectProps={CustomMantineEditSelectProps}

            // Expand detail panel
            // enableExpanding={grouping?.length > 0}
            enableExpanding={grouping?.length > 0 || enableExpand}
            enableExpandAll={false}

            // filters
            enableGlobalFilterModes={false}
            enableGlobalFilter={false}
            enableColumnFilterModes={enableFilter}

            // pagination
            enablePagination={enablePagination || false}
            paginationDisplayMode={'default'}
            // positionToolbarAlertBanner={'top'}

            // rows
            enableMultiRowSelection={enableSelection}
            enableRowSelection={enableSelection}
            enableSelectAll={enableSelection}
            enableSubRowSelection={enableSelection}
            enableRowActions={CustomRowActionMenuItems}
            enableRowNumbers={false}
            enableRowVirtualization={rowVirtualization}

            // columns
            enableColumnDragging={true}
            enableColumnOrdering={true}
            enableColumnResizing={true}
            enableColumnVirtualization={columnVirtualization}

            // mantine table props
            mantineTableContainerProps={{
                sx: {
                    maxHeight: containerHeight(grouping, selection, enablePagination),
                    minHeight: containerHeight(grouping, selection, enablePagination),
                    'thead': {
                        // we need to add z-index as 5 here if we display the display panel
                        zIndex: 5,
                    },
                    // 'tr.mantine-TableBodyCell-DetailPanel': {
                    //     width: '100%',
                    //     zIndex: 5,
                    //     backgroundColor: 'gray'
                    // },
                },
            }}

            // Top toolbar props
            mantineTopToolbarProps={{
                sx: {
                    maxHeight: grouping?.length > 0 && (selection ? Object.keys(selection).length > 0 : false) ? '180px' :
                        grouping?.length > 0 || (selection ? Object.keys(selection).length > 0 : false) ? '120px' : '60px',
                    minHeight: grouping?.length > 0 && (selection ? Object.keys(selection).length > 0 : false) ? '180px' :
                        grouping?.length > 0 || (selection ? Object.keys(selection).length > 0 : false) ? '120px' : '60px',
                    zIndex: 6,
                }
            }}

            // Bottom toolbar props
            mantineBottomToolbarProps={{
                sx: {
                    // maxHeight: 0,
                    minHeight: enablePagination ? '70px' : '0px',
                    maxHeight: enablePagination ? '70px' : '0px',
                    height: enablePagination ? '70px' : '0px',
                    padding: 0,
                    placeItems: 'center',
                    verticalAlign: 'center',
                    '& *': {
                        padding: 0,
                    },
                    '& input': {
                        padding: '5px 12px 5px 12px'
                    }
                }
            }}

            mantineTableFooterRowProps={{
                sx: {
                    padding: 0,
                    height: '100%'
                }
            }}

            mantineTableFooterCellProps={{
                sx: {
                    padding: 0,
                    height: '100%'
                }
            }}

            mantineDetailPanelProps={{
                sx: {
                    width: '100%',
                    backgroundColor: detailPanelBG ? detailPanelBG : 'rgb(225,225,225)',
                }
            }}

            mantineTableBodyRowProps={{
                sx: {
                    height: CustomDetailPanel ? null : '20px',
                }
            }}

            mantineTableBodyCellProps={{
                // height: '20px',
                sx: {
                    height: '10px',
                },
            }}

            mantineTableProps={{
                striped: false,
            }}

            mantineSelectAllCheckboxProps={{
                size: 'xs',
                color: 'red',
                variant: 'filled',
            }}

            // Pagination props
            mantinePaginationProps={{
                rowsPerPageOptions: ['10', '30', '50', '75', '100', '250'],
                showRowsPerPage: true,
                withEdges: true,
                radius: 'xs',
                size: 'xs',
            }}

            mantineToolbarAlertBannerProps={{color: 'dark', variant: 'light',}}
            mantineToolbarAlertBannerBadgeProps={{color: 'dark', variant: 'filled'}}
            mantineSelectCheckboxProps={{color: 'red', size: 'xs', variant: 'filled'}}

            renderToolbarAlertBannerContent={({table, groupedAlert, selectedAlert}) => {
                return (
                    CustomToolbarAlertBannerContent ?
                        <CustomToolbarAlertBannerContent table={table} groupedAlert={groupedAlert}
                                                         selectedAlert={selectedAlert}/> :
                        <Flex justify="space-between">
                            <Flex p="6px" gap="xl">
                                {groupedAlert?.props?.children}
                            </Flex>
                        </Flex>
                )
            }
            }

            // Render Custom Functions
            renderTopToolbarCustomActions={({table}) => {
                let allRows = table?.getPreFilteredRowModel()
                let filteredRows = table?.getFilteredRowModel()

                return (
                    <>
                        <Group spacing={'xs'}>
                            <Menu width={175} shadow={'xl'} position={'bottom-start'}
                                  transitionProps={{transition: 'scale-y', duration: 700}}>
                                <Menu.Target>
                                    <Button
                                        variant={'filled'} color={'accenture.6'} size={'sm'}
                                        leftIcon={<Image src={gearIcon} height={17} width={17}/>}
                                        styles={(theme) => ({
                                            root: {
                                                backgroundColor: theme.fn.lighten('#A100FF', 0.80),
                                                '&:not([data-disabled])': theme.fn.hover({
                                                    backgroundColor: theme.fn.lighten('#A100FF', 0.55),
                                                }),
                                            }
                                        })}
                                    >
                                        <Text fw={700} color={'black'}>Menu</Text>
                                    </Button>
                                </Menu.Target>

                                <Menu.Dropdown>
                                    <Menu.Label>Refetch data</Menu.Label>
                                    <CustomMantineMenuItem onClick={() => refetch()}>
                                        <Group>
                                            <Image src={refreshIcon} height={17} width={17}/>
                                            <Text fw={700} color={'black'} size={'xs'}>Refresh</Text>
                                        </Group>
                                    </CustomMantineMenuItem>

                                    {
                                        CustomTopToolbarApplications ?
                                            <>
                                                <Menu.Label>Applications</Menu.Label>
                                                <CustomTopToolbarApplications/>
                                            </> :
                                            <></>
                                    }

                                    {
                                        CustomTopToolbarTools ?
                                            <>
                                                <Menu.Label>Tools</Menu.Label>
                                                <CustomTopToolbarTools/>
                                            </> :
                                            <></>
                                    }
                                </Menu.Dropdown>
                            </Menu>
                            {
                                CustomTopToolbarFilters ?
                                    <Menu width={filtersWidth ? filtersWidth : 175} shadow={'xl'}
                                          position={'bottom-start'}
                                          transitionProps={{transition: 'scale-y', duration: 700}}>
                                        <Menu.Target>
                                            <Button
                                                variant={'filled'} size={'sm'}
                                                leftIcon={<Image src={filterIcon} height={17} width={17}/>}
                                                styles={(theme) => ({
                                                    root: {
                                                        backgroundColor: theme.fn.lighten('#c48300', 0.80),
                                                        '&:not([data-disabled])': theme.fn.hover({
                                                            backgroundColor: theme.fn.lighten('#c48300', 0.55),
                                                        }),
                                                    }
                                                })}
                                            >
                                                <Text fw={700} color={'black'}>Filters</Text>
                                            </Button>
                                        </Menu.Target>
                                        <Menu.Dropdown>
                                            {
                                                noFilterLabel ?
                                                    <></> :
                                                    <Menu.Label>Custom Filters</Menu.Label>
                                            }
                                            <CustomTopToolbarFilters/>
                                        </Menu.Dropdown>

                                    </Menu>
                                    :
                                    <></>
                            }
                        </Group>

                        {
                            !enablePagination ?
                                <Text style={{color: 'dark'}} fz={'xs'} color={'dimmed'}>
                                    Presenting {
                                    filteredRows.rows.length === allRows.rows.length ?
                                        allRows.rows.length :
                                        filteredRows.rows.length} of {allRows.rows.length} records
                                </Text>
                                :
                                <></>
                        }
                    </>
                )
            }}

            renderRowActionMenuItems={({table, row}) => {
                return (
                    CustomRowActionMenuItems ?
                        <CustomRowActionMenuItems table={table} row={row}/> :
                        <></>
                )
            }}

            renderEmptyRowsFallback={
                () => {
                    return (
                        <Container size={'lg'}>
                            <Text fw={700} fz={'lg'} ta={'center'} color={'accenture.6'}>Apologies, but no data
                                returned 😳</Text>
                            {
                                isError ?
                                    <>
                                        <Divider my={'sm'}/>
                                        <Text fw={500} fz={'md'} ta={'center'} color={'accenture.6'}>Error
                                            Details:</Text>
                                        <Text fw={300} fz={'sm'} ta={'center'}
                                              color={'accenture.6'}>{error ? error?.data?.message : "There was some error..."
                                        || error ? error?.error : "There was some error..."}</Text>
                                    </>
                                    :
                                    <></>
                            }
                        </Container>
                    )
                }
            }

            renderDetailPanel={CustomDetailPanel}
            renderBottomToolbarCustomActions={CustomBottomToolbar}

            // onChange events
            onSortingChange={setSorting}
            onColumnFiltersChange={setFiltering}
            onGlobalFilterChange={setGlobalFilter}
            onGroupingChange={setGrouping}
            onColumnVisibilityChange={setVisibility}
            onColumnOrderChange={setOrder}
            onRowSelectionChange={setSelection || null}

            // Virtualization
            rowVirtualizerInstanceRef={rowVirtualizerInstanceRef} //optional
            rowVirtualizerProps={{overscan: 8, estimateSize: () => 30}} //optionally customize the row virtualizer
            columnVirtualizerProps={{overscan: 5}} //optionally customize the column virtualizer

            // state for the table
            state={{
                columnFilters: filtering,
                globalFilter: globalFilter,
                isLoading: isLoading || isFetching,
                showAlertBanner: false,
                showGlobalFilter: false,
                showProgressBars: isLoading || isFetching,
                sorting: sorting,
                grouping: grouping,
                columnVisibility: visibility,
                columnOrder: order,
                rowSelection: selection || {}
            }}
        />
    )

Screenshots or Videos (Optional)

No response

Do you intend to try to help solve this bug with your own PR?

No, because I do not know how

Terms

dafer660 commented 2 months ago

This does not happen in V2, so I guess it's something related to my code, or something I am doing wrong. To not waste time of the amazing developers, I believe this can be closed, if it's too time consuming.

Thank you for everything!

Daniel F.