Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 33 additions & 15 deletions src/react/use-zero-virtualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ const NUM_ROWS_FOR_LOADING_SKELETON = 1;
*
* @typeParam TStartRow - The type of the start row data used for pagination anchoring
*/
export type PermalinkHistoryState<TStartRow> = Readonly<{
export type PermalinkHistoryState<
TStartRow,
TListContextParams = unknown,
> = Readonly<{
/** The anchor point for pagination (includes position, direction, and start row data) */
anchor: Anchor<TStartRow>;
/** The scroll position in pixels from the top of the scrollable container */
Expand All @@ -40,6 +43,8 @@ export type PermalinkHistoryState<TStartRow> = Readonly<{
hasReachedStart: boolean;
/** Whether the virtualizer has reached the end of the list */
hasReachedEnd: boolean;
/** The list context params active when this state was saved (used to invalidate stale state) */
listContextParams: TListContextParams;
}>;

const TOP_ANCHOR = Object.freeze({
Expand Down Expand Up @@ -219,6 +224,16 @@ export function useZeroVirtualizer<
TRow,
TStartRow
>): ZeroVirtualizerResult<TScrollElement, TItemElement, TRow> {
// Only restore from permalinkState if its listContextParams matches the current context.
// This prevents restoring stale scroll positions when filters/sort change.
const effectivePermalinkState = useMemo(() => {
if (!permalinkState) return null;
if (permalinkState.listContextParams !== listContextParams) {
return null;
}
return permalinkState;
}, [permalinkState, listContextParams]);

// Initialize paging state from permalinkState directly to avoid Strict Mode double-mount rows
const [
{
Expand All @@ -234,16 +249,17 @@ export function useZeroVirtualizer<
pagingReducer<TListContextParams, TStartRow>,
undefined,
(): PagingState<TListContextParams, TStartRow> => {
const anchor = permalinkState
? permalinkState.anchor
const anchor = effectivePermalinkState
? effectivePermalinkState.anchor
: permalinkID
? createPermalinkAnchor(permalinkID)
: TOP_ANCHOR;
return {
estimatedTotal:
permalinkState?.estimatedTotal ?? NUM_ROWS_FOR_LOADING_SKELETON,
hasReachedStart: permalinkState?.hasReachedStart ?? false,
hasReachedEnd: permalinkState?.hasReachedEnd ?? false,
effectivePermalinkState?.estimatedTotal ??
NUM_ROWS_FOR_LOADING_SKELETON,
hasReachedStart: effectivePermalinkState?.hasReachedStart ?? false,
hasReachedEnd: effectivePermalinkState?.hasReachedEnd ?? false,
queryAnchor: {
anchor,
listContextParams,
Expand Down Expand Up @@ -302,8 +318,8 @@ export function useZeroVirtualizer<
}
: getItemKey,
initialOffset: () => {
if (permalinkState?.scrollTop !== undefined) {
return permalinkState.scrollTop;
if (effectivePermalinkState?.scrollTop !== undefined) {
return effectivePermalinkState.scrollTop;
}
if (anchor.kind === 'permalink') {
// TODO: Support dynamic item sizes
Expand Down Expand Up @@ -346,6 +362,7 @@ export function useZeroVirtualizer<
estimatedTotal,
hasReachedStart,
hasReachedEnd,
listContextParams,
});
}, 100);

Expand All @@ -358,6 +375,7 @@ export function useZeroVirtualizer<
hasReachedEnd,
isListContextCurrent,
onPermalinkStateChange,
listContextParams,
]);

useEffect(() => {
Expand Down Expand Up @@ -437,14 +455,14 @@ export function useZeroVirtualizer<
// Use layoutEffect to restore scroll position synchronously to avoid visual jumps
useLayoutEffect(() => {
if (!isListContextCurrent) {
if (permalinkState) {
virtualizer.scrollToOffset(permalinkState.scrollTop);
if (effectivePermalinkState) {
virtualizer.scrollToOffset(effectivePermalinkState.scrollTop);
dispatch({
type: 'RESET_STATE',
estimatedTotal: permalinkState.estimatedTotal,
hasReachedStart: permalinkState.hasReachedStart,
hasReachedEnd: permalinkState.hasReachedEnd,
anchor: permalinkState.anchor,
estimatedTotal: effectivePermalinkState.estimatedTotal,
hasReachedStart: effectivePermalinkState.hasReachedStart,
hasReachedEnd: effectivePermalinkState.hasReachedEnd,
anchor: effectivePermalinkState.anchor,
listContextParams,
});
} else if (permalinkID) {
Expand Down Expand Up @@ -475,7 +493,7 @@ export function useZeroVirtualizer<
}
}, [
isListContextCurrent,
permalinkState,
effectivePermalinkState,
permalinkID,
virtualizer,
listContextParams,
Expand Down