Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore List Scroll Position on Edit and Create Views side effects #9774

Merged
merged 10 commits into from
Apr 16, 2024
1 change: 1 addition & 0 deletions packages/ra-core/src/routing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export * from './useBasename';
export * from './useCreatePath';
export * from './useRedirect';
export * from './useScrollToTop';
export * from './useRestoreScrollPosition';
export * from './types';
export * from './TestMemoryRouter';
2 changes: 1 addition & 1 deletion packages/ra-core/src/routing/useRedirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const useRedirect = () => {
} else {
// redirection to an internal link
navigate(createPath({ resource, id, type: redirectTo }), {
state: { _scrollToTop: true, ...state },
state: { _scrollToTop: redirectTo !== 'list', ...state },
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
});
return;
}
Expand Down
49 changes: 49 additions & 0 deletions packages/ra-core/src/routing/useRestoreScrollPosition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useEffect } from 'react';
import { useStore } from '../store';
import { debounce } from 'lodash';

/**
* A hook that tracks the scroll position and restores it when the component mounts.
* @param key The key under which to store the scroll position in the store
* @param debounceMs The debounce time in milliseconds
*
* @example
* import { ListBase, useRestoreScrollPosition } from 'ra-core';
*
* const MyCustomList = (props) => {
* useRestoreScrollPosition('my-list');
* return <ListBase {...props} />;
* };
*/
export const useRestoreScrollPosition = (key: string, debounceMs = 250) => {
const position = useTrackScrollPosition(key, debounceMs);

useEffect(() => {
if (position != null) {
window.scrollTo(0, position);
}
// We only want to run this effect on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};

export const useTrackScrollPosition = (key: string, debounceMs = 250) => {
const [position, setPosition] = useStore(key);

useEffect(() => {
if (typeof window === 'undefined') {
return;
}
const handleScroll = debounce(() => {
setPosition(window.scrollY);
}, debounceMs);

window.addEventListener('scroll', handleScroll);

return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [debounceMs, setPosition]);

return position;
};
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/src/list/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const List = <RecordType extends RaRecord = any>({
sort={sort}
storeKey={storeKey}
>
<ListView<RecordType> {...rest} />
<ListView<RecordType> storeKey={storeKey} {...rest} />
</ListBase>
);

Expand Down
30 changes: 29 additions & 1 deletion packages/ra-ui-materialui/src/list/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import PropTypes from 'prop-types';
import { SxProps } from '@mui/system';
import Card from '@mui/material/Card';
import clsx from 'clsx';
import { ComponentPropType, useListContext, RaRecord } from 'ra-core';
import {
ComponentPropType,
useListContext,
useRestoreScrollPosition,
RaRecord,
} from 'ra-core';

import { Title, TitlePropType } from '../layout/Title';
import { ListToolbar } from './ListToolbar';
Expand Down Expand Up @@ -33,6 +38,7 @@ export const ListView = <RecordType extends RaRecord = any>(
component: Content = DefaultComponent,
title,
empty = defaultEmpty,
storeKey,
...rest
} = props;
const {
Expand All @@ -43,6 +49,11 @@ export const ListView = <RecordType extends RaRecord = any>(
filterValues,
resource,
} = useListContext<RecordType>();
useRestoreScrollPosition(
storeKey
? `${storeKey}/listScrollPosition`
: `${resource}/listScrollPosition`
);

if (!children || (!data && isPending && emptyWhileLoading)) {
return null;
Expand Down Expand Up @@ -306,6 +317,23 @@ export interface ListViewProps {
*/
title?: string | ReactElement;

/**
* The key to use to store the current filter & sort. Pass false to disable.
*
* @see https://marmelab.com/react-admin/List.html#storekey
* @example
* const NewerBooks = () => (
* <List
* resource="books"
* storeKey="newerBooks"
* sort={{ field: 'year', order: 'DESC' }}
* >
* ...
* </List>
* );
*/
storeKey?: string | false;

/**
* The CSS styles to apply to the component.
*
Expand Down
Loading