Fix optimistic rendering issues on views (#2851)

* Fix optimistic rendering issues on views

* Remove virtualizer
This commit is contained in:
Charles Bochet
2023-12-06 16:55:09 +01:00
committed by GitHub
parent 93decaceab
commit 076a67b0e2
12 changed files with 229 additions and 318 deletions
@@ -1,92 +1,33 @@
import { useContext } from 'react';
import { useInView } from 'react-intersection-observer';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilValue } from 'recoil';
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
import {
RecordTableRow,
StyledRow,
} from '@/ui/object/record-table/components/RecordTableRow';
import { RecordTableBodyFetchMoreLoader } from '@/ui/object/record-table/components/RecordTableBodyFetchMoreLoader';
import { RecordTableRow } from '@/ui/object/record-table/components/RecordTableRow';
import { RowIdContext } from '@/ui/object/record-table/contexts/RowIdContext';
import { RowIndexContext } from '@/ui/object/record-table/contexts/RowIndexContext';
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
import { isFetchingRecordTableDataState } from '@/ui/object/record-table/states/isFetchingRecordTableDataState';
import { tableRowIdsState } from '@/ui/object/record-table/states/tableRowIdsState';
import { getRecordTableScopedStates } from '@/ui/object/record-table/utils/getRecordTableScopedStates';
import { ScrollWrapperContext } from '@/ui/utilities/scroll/components/ScrollWrapper';
import RenderIfVisible from '@/ui/utilities/virtualizer/RenderIfVisible';
export const RecordTableBody = () => {
const { scopeId } = useRecordTable();
const onLastRowVisible = useRecoilCallback(
({ set }) =>
async (inView: boolean) => {
const { tableLastRowVisibleState } = getRecordTableScopedStates({
recordTableScopeId: scopeId,
});
set(tableLastRowVisibleState, inView);
},
[scopeId],
);
const { ref: lastTableRowRef } = useInView({
onChange: onLastRowVisible,
});
const tableRowIds = useRecoilValue(tableRowIdsState);
const [isFetchingMoreObjects] = useRecoilState(
isFetchingMoreRecordsFamilyState(scopeId),
);
const isFetchingRecordTableData = useRecoilValue(
isFetchingRecordTableDataState,
);
const lastRowId = tableRowIds[tableRowIds.length - 1];
const scrollWrapperRef = useContext(ScrollWrapperContext);
if (isFetchingRecordTableData) {
return <></>;
}
return (
<>
{tableRowIds.map((rowId, rowIndex) => (
{tableRowIds.slice().map((rowId, rowIndex) => (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={rowIndex}>
<RenderIfVisible
rootElement="tbody"
placeholderElement="tr"
defaultHeight={32}
initialVisible={rowIndex < 30}
root={scrollWrapperRef.current}
>
<RecordTableRow
key={rowId}
ref={
rowId === lastRowId && rowIndex > 30
? lastTableRowRef
: undefined
}
rowId={rowId}
/>
</RenderIfVisible>
<RecordTableRow key={rowId} rowId={rowId} />
</RowIndexContext.Provider>
</RowIdContext.Provider>
))}
<tbody>
{isFetchingMoreObjects && (
<StyledRow selected={false}>
<td style={{ height: 50 }} colSpan={1000}>
Loading more...
</td>
</StyledRow>
)}
</tbody>
<RecordTableBodyFetchMoreLoader />
</>
);
};
@@ -1,28 +1,40 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
import { isDefined } from '~/utils/isDefined';
export const RecordTableBodyEffect = () => {
const {
fetchMoreRecords: fetchMoreObjects,
records,
setRecordTableData,
queryStateIdentifier,
} = useObjectRecordTable();
const { tableLastRowVisibleState } = useRecordTableScopedStates();
const tableLastRowVisible = useRecoilValue(tableLastRowVisibleState);
const [tableLastRowVisible, setTableLastRowVisible] = useRecoilState(
tableLastRowVisibleState,
);
const isFetchingMoreObjects = useRecoilValue(
isFetchingMoreRecordsFamilyState(queryStateIdentifier),
);
useEffect(() => {
setRecordTableData(records);
}, [records, setRecordTableData]);
useEffect(() => {
if (tableLastRowVisible && isDefined(fetchMoreObjects)) {
if (tableLastRowVisible && !isFetchingMoreObjects) {
fetchMoreObjects();
}
}, [fetchMoreObjects, tableLastRowVisible]);
}, [
fetchMoreObjects,
isFetchingMoreObjects,
setTableLastRowVisible,
tableLastRowVisible,
]);
return <></>;
};
@@ -0,0 +1,53 @@
import { useInView } from 'react-intersection-observer';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
import { StyledRow } from '@/ui/object/record-table/components/RecordTableRow';
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
import { isFetchingRecordTableDataState } from '@/ui/object/record-table/states/isFetchingRecordTableDataState';
import { getRecordTableScopedStates } from '@/ui/object/record-table/utils/getRecordTableScopedStates';
export const RecordTableBodyFetchMoreLoader = () => {
const { queryStateIdentifier } = useObjectRecordTable();
const { scopeId } = useRecordTable();
const isFetchingMoreObjects = useRecoilValue(
isFetchingMoreRecordsFamilyState(queryStateIdentifier),
);
const isFetchingRecordTableData = useRecoilValue(
isFetchingRecordTableDataState,
);
const onLastRowVisible = useRecoilCallback(
({ set }) =>
async (inView: boolean) => {
const { tableLastRowVisibleState } = getRecordTableScopedStates({
recordTableScopeId: scopeId,
});
set(tableLastRowVisibleState, inView);
},
[scopeId],
);
const { ref: tbodyRef } = useInView({
onChange: onLastRowVisible,
});
if (isFetchingRecordTableData) {
return <></>;
}
return (
<tbody ref={tbodyRef}>
{isFetchingMoreObjects && (
<StyledRow selected={false}>
<td style={{ height: 50 }} colSpan={1000}>
Loading more...
</td>
</StyledRow>
)}
</tbody>
);
};
@@ -1,7 +1,10 @@
import { forwardRef } from 'react';
import { useContext } from 'react';
import { useInView } from 'react-intersection-observer';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { ScrollWrapperContext } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { ColumnContext } from '../contexts/ColumnContext';
import { useRecordTableScopedStates } from '../hooks/internal/useRecordTableScopedStates';
import { useCurrentRowSelected } from '../record-table-row/hooks/useCurrentRowSelected';
@@ -18,36 +21,53 @@ type RecordTableRowProps = {
rowId: string;
};
export const RecordTableRow = forwardRef<
HTMLTableRowElement,
RecordTableRowProps
>(({ rowId }, ref) => {
const StyledPlaceholder = styled.td`
height: 30px;
`;
export const RecordTableRow = ({ rowId }: RecordTableRowProps) => {
const { visibleTableColumnsSelector } = useRecordTableScopedStates();
const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector);
const { currentRowSelected } = useCurrentRowSelected();
const scrollWrapperRef = useContext(ScrollWrapperContext);
const { ref: elementRef, inView } = useInView({
root: scrollWrapperRef.current,
rootMargin: '1000px',
});
return (
<StyledRow
ref={ref}
ref={elementRef}
data-testid={`row-id-${rowId}`}
selected={currentRowSelected}
data-selectable-id={rowId}
>
<td>
<CheckboxCell />
</td>
{[...visibleTableColumns]
.sort((columnA, columnB) => columnA.position - columnB.position)
.map((column, columnIndex) => {
return (
<ColumnContext.Provider value={column} key={column.fieldMetadataId}>
<RecordTableCell cellIndex={columnIndex} />
</ColumnContext.Provider>
);
})}
<td></td>
{inView ? (
<>
<td>
<CheckboxCell />
</td>
{[...visibleTableColumns]
.sort((columnA, columnB) => columnA.position - columnB.position)
.map((column, columnIndex) => {
return (
<ColumnContext.Provider
value={column}
key={column.fieldMetadataId}
>
<RecordTableCell cellIndex={columnIndex} />
</ColumnContext.Provider>
);
})}
<td></td>
</>
) : (
<StyledPlaceholder />
)}
</StyledRow>
);
});
};