fix: board view loads all records instead of showing skeleton placeholders (#18824)

## Summary

- The record board (Kanban view) only loaded the first 10 records per
column, then showed skeleton placeholder cards for the rest without ever
fetching the remaining data
- **Root cause**: The `RecordBoardFetchMoreInViewTriggerComponent`
(IntersectionObserver) is positioned at the bottom of the entire board.
With 10 real cards + 10 skeleton cards per column (~3500px of content),
the trigger div was pushed far beyond the 1600px `rootMargin` detection
zone, so `recordBoardShouldFetchMore` never became `true` and fetch-more
was never triggered
- **Fix**: The initial query now sets `recordBoardShouldFetchMore =
true` when columns need more data, and `triggerRecordBoardFetchMore`
uses an internal `while` loop to load all remaining pages in a single
invocation — making it immune to the InView component racing to reset
the flag between React render cycles
This commit is contained in:
Charles Bochet
2026-03-21 17:20:53 +01:00
committed by GitHub
parent c01fc3bafb
commit 03a2abb305
2 changed files with 34 additions and 1 deletions
@@ -2,15 +2,19 @@ import { styled } from '@linaria/react';
import { useEffect } from 'react';
import { useInView } from 'react-intersection-observer';
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { RECORD_BOARD_COLUMN_PADDING_AND_BORDER_WIDTH } from '@/object-record/record-board/constants/RecordBoardColumnPaddingAndBorderWidth';
import { RECORD_BOARD_COLUMN_WIDTH } from '@/object-record/record-board/constants/RecordBoardColumnWidth';
import { RECORD_BOARD_QUERY_PAGE_SIZE } from '@/object-record/record-board/constants/RecordBoardQueryPageSize';
import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState';
import { recordBoardShouldFetchMoreComponentState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreComponentState';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
import { recordIndexRecordGroupsAreInInitialLoadingComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupsAreInInitialLoadingComponentState';
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
import { useAtomComponentFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilySelectorValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { ViewType } from '@/views/types/ViewType';
@@ -20,6 +24,16 @@ const StyledFetchMoreTriggerDiv = styled.div<{ width: number }>`
min-width: ${({ width }) => width}px;
`;
// RecordCardHeaderContainer: height (24px) + padding top spacing(2) + padding bottom spacing(1)
const BOARD_CARD_HEADER_HEIGHT = 24 + 8 + 4;
// Per field row: skeleton height + RecordCardBodyContainer padding-bottom spacing(2) + StyledBodyContainer gap spacing(0.5)
const BOARD_CARD_FIELD_ROW_HEIGHT =
SKELETON_LOADER_HEIGHT_SIZES.standard.s + 8 + 2;
// StyledBodyContainer padding (4+4) + card border (2×1px) + StyledSkeletonCardContainer margin-bottom spacing(2)
const BOARD_CARD_CHROME_HEIGHT = 8 + 2 + 8;
export const RecordBoardFetchMoreInViewTriggerComponent = () => {
const [recordBoardShouldFetchMore, setRecordBoardShouldFetchMore] =
useAtomComponentState(recordBoardShouldFetchMoreComponentState);
@@ -32,10 +46,21 @@ export const RecordBoardFetchMoreInViewTriggerComponent = () => {
recordBoardIsFetchingMoreComponentState,
);
const visibleRecordFields = useAtomComponentSelectorValue(
visibleRecordFieldsComponentSelector,
);
const estimatedCardHeight =
BOARD_CARD_HEADER_HEIGHT +
visibleRecordFields.length * BOARD_CARD_FIELD_ROW_HEIGHT +
BOARD_CARD_CHROME_HEIGHT;
const rootMargin = `${estimatedCardHeight * RECORD_BOARD_QUERY_PAGE_SIZE * 2}px`;
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
const { ref, inView } = useInView({
rootMargin: '1600px',
rootMargin,
root: scrollWrapperHTMLElement,
});
@@ -7,6 +7,7 @@ import { RECORD_BOARD_QUERY_PAGE_SIZE } from '@/object-record/record-board/const
import { recordBoardCurrentGroupByQueryOffsetComponentState } from '@/object-record/record-board/states/recordBoardCurrentGroupByQueryOffsetComponentState';
import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState';
import { recordBoardShouldFetchMoreComponentState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreComponentState';
import { recordBoardShouldFetchMoreInColumnComponentFamilyState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreInColumnComponentFamilyState';
import { recordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/selectors/recordGroupDefinitionsComponentSelector';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
@@ -66,6 +67,11 @@ export const useTriggerRecordBoardFetchMore = () => {
recordBoardIsFetchingMoreComponentState,
);
const recordBoardShouldFetchMoreCallbackState =
useAtomComponentStateCallbackState(
recordBoardShouldFetchMoreComponentState,
);
const triggerRecordBoardFetchMore = useCallback(async () => {
const isAlreadyFetchingMore = store.get(recordBoardIsFetchingMore);
@@ -97,6 +103,7 @@ export const useTriggerRecordBoardFetchMore = () => {
.filter(isDefined);
if (!isNonEmptyArray(recordGroupValuesThatShouldBeFetched)) {
store.set(recordBoardShouldFetchMoreCallbackState, false);
cleanStateBeforeExit();
return;
@@ -217,6 +224,7 @@ export const useTriggerRecordBoardFetchMore = () => {
recordBoardIsFetchingMore,
recordBoardCurrentGroupByQueryOffsetCallbackState,
recordBoardShouldFetchMoreInColumnFamilyCallbackState,
recordBoardShouldFetchMoreCallbackState,
combinedFilters,
recordIndexGroupFieldMetadataItem,
]);