Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion docs/PrevNextButtons.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,9 @@ export const MyAdmin = () => (
`<PrevNextButtons>` can get the current list parameters (sort and filters) from the store.
This prop is useful if you specified a custom `storeKey` for a `<List>` and you want `<PrevNextButtons>` to use the same stored parameters.

See [`storeKey` in `<List>`](./List.md#storekey) for more information.
If you don't want `<PrevNextButtons>` to read the parameters stored by the `<List>` (for instance, to let users browse a thread of messages from a `<Show>` view regardless of how they last filtered the messages list), pass `false`: the pager will then rely only on its own `sort`, `filter` and `filterDefaultValues` props.

See [`storeKey` in `<List>`](./List.md#storekey) for more information.

```tsx
export const MyAdmin = () => (
Expand Down
38 changes: 21 additions & 17 deletions packages/ra-core/src/controller/usePrevNextController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,26 +130,31 @@ export const usePrevNextController = <RecordType extends RaRecord = any>(
);
}

const defaultParams: ListParams = {
filter: filterDefaultValues,
order: initialSort.order,
sort: initialSort.field,
page: 1,
perPage: 10,
displayedFilters: {},
};
// As we can't conditionally call a hook, if the storeKey is false,
// we'll ignore the storedParams variable later on and use the
// props-derived defaults instead.
const [storedParams] = useStore<ListParams>(
storeKey || `${resource}.listParams`,
{
filter: filterDefaultValues,
order: initialSort.order,
sort: initialSort.field,
page: 1,
perPage: 10,
displayedFilters: {},
}
defaultParams
);
const listParams = storeKey === false ? defaultParams : storedParams;

const dataProvider = useDataProvider();
const queryClient = useQueryClient();
const pagination = { page: 1, perPage: limit };
const sort = {
field: storedParams.sort,
order: storedParams.order,
field: listParams.sort,
order: listParams.order,
};
const filter = { ...storedParams.filter, ...permanentFilter };
const filter = { ...listParams.filter, ...permanentFilter };
const { meta, ...otherQueryOptions } = queryOptions;
const params = { pagination, sort, filter, meta };

Expand All @@ -163,20 +168,20 @@ export const usePrevNextController = <RecordType extends RaRecord = any>(
{
...params,
pagination: {
page: storedParams.page,
perPage: storedParams.perPage,
page: listParams.page,
perPage: listParams.perPage,
},
},
]);
const recordIndexInQueryData = queryData?.data?.findIndex(
r => r.id === record?.id
);
const isRecordIndexFirstInNonFirstPage =
recordIndexInQueryData === 0 && storedParams.page > 1;
recordIndexInQueryData === 0 && listParams.page > 1;
const isRecordIndexLastInNonLastPage =
queryData?.data && queryData?.total
? recordIndexInQueryData === queryData?.data?.length - 1 &&
storedParams.page < queryData?.total / storedParams.perPage
listParams.page < queryData?.total / listParams.perPage
: undefined;
const canUseCacheData =
record &&
Expand Down Expand Up @@ -249,8 +254,7 @@ export const usePrevNextController = <RecordType extends RaRecord = any>(
? undefined
: index +
(canUseCacheData
? (storedParams.perPage ?? 0) *
((storedParams.page ?? 1) - 1)
? (listParams.perPage ?? 0) * ((listParams.page ?? 1) - 1)
: 0),
total: canUseCacheData ? queryData?.total : data?.total,
error,
Expand Down
38 changes: 38 additions & 0 deletions packages/ra-ui-materialui/src/button/PrevNextButtons.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
WithFilter,
WithLimit,
WithQueryFilter,
WithoutStoreKey,
} from './PrevNextButtons.stories';

describe('<PrevNextButtons />', () => {
Expand Down Expand Up @@ -109,6 +110,43 @@ describe('<PrevNextButtons />', () => {
});
});

describe('storeKey', () => {
it('should ignore the stored list params when storeKey is false', async () => {
const data = {
customers: Array.from(Array(900).keys()).map(id => {
const first_name = `first_name_${id}`;
const last_name = `last_name_${id}`;
const email = `first_name_${id}.last_name_${id}@example.com`;

return {
id,
first_name,
last_name,
email,
city: `city_${Math.floor(id / 50)}`,
};
}),
};
const dataProvider = fakeRestDataProvider(data);
const spy = jest.spyOn(dataProvider, 'getList');
render(<WithoutStoreKey customDataProvider={dataProvider} />);
const input = await screen.findByLabelText('Search');
fireEvent.change(input, { target: { value: 'city_0' } });
await screen.findByText('1-10 of 50');
const item = await screen.findByText('first_name_9');
fireEvent.click(item);
await screen.findByRole('navigation');
await screen.findByText('11 / 900');
expect(spy).toHaveBeenCalledWith('customers', {
pagination: { page: 1, perPage: 1000 },
sort: { field: 'first_name', order: 'DESC' },
filter: {},
meta: undefined,
signal: undefined,
});
});
});

describe('limit', () => {
it('should render the total number of items, even with a limit', async () => {
render(<WithLimit />);
Expand Down
60 changes: 60 additions & 0 deletions packages/ra-ui-materialui/src/button/PrevNextButtons.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,66 @@ export const WithStoreKey = () => (
</TestMemoryRouter>
);

export const WithoutStoreKey = ({ customDataProvider = dataProvider }: any) => (
<TestMemoryRouter>
<AdminContext
dataProvider={customDataProvider}
i18nProvider={i18nProvider}
>
<AdminUI>
<Resource
name="customers"
list={
<ListGuesser
filters={[
<TextInput
label="Search"
source="q"
alwaysOn
key="q"
/>,
]}
/>
}
edit={
<CustomerEdit
actions={
<MyTopToolbar>
<PrevNextButtons
storeKey={false}
sort={{
field: 'first_name',
order: 'DESC',
}}
/>
<ShowButton />
</MyTopToolbar>
}
/>
}
show={
<CustomerShow
actions={
<MyTopToolbar>
<PrevNextButtons
linkType="show"
storeKey={false}
sort={{
field: 'first_name',
order: 'DESC',
}}
/>
<EditButton />
</MyTopToolbar>
}
/>
}
/>
</AdminUI>
</AdminContext>
</TestMemoryRouter>
);

export const WithFilter = () => (
<TestMemoryRouter>
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
Expand Down
Loading