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

Enable Store sync when disableSyncWithLocation is true #10187

Merged
merged 9 commits into from
Sep 11, 2024
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
32 changes: 30 additions & 2 deletions docs/List.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ const BoolkList = () => (

By default, react-admin synchronizes the `<List>` parameters (sort, pagination, filters) with the query string in the URL (using `react-router` location) and the [Store](./Store.md).

When you use a `<List>` component anywhere else than as `<Resource list>`, you may want to disable this synchronization to keep the parameters in a local state, independent for each `<List>` instance. This allows to have multiple lists on a single page. The drawback is that a hit on the "back" button doesn't restore the previous list parameters. To do so, pass the `disableSyncWithLocation` prop.
When you use a `<List>` component anywhere else than as `<Resource list>`, you may want to disable this synchronization to keep the parameters in a local state, independent for each `<List>` instance. This allows to have multiple lists on a single page. To do so, pass the `disableSyncWithLocation` prop. The drawback is that a hit on the "back" button doesn't restore the previous list parameters.

{% raw %}
```jsx
Expand Down Expand Up @@ -445,7 +445,35 @@ const Dashboard = () => (
```
{% endraw %}

**Tip**: As `disableSyncWithLocation` also disables the persistence of the list parameters in the Store, any custom string specified in the `storeKey` prop is ignored when `disableSyncWithLocation` is set to `true`.
**Tip**: `disableSyncWithLocation` also disables the persistence of the list parameters in the Store by default. To enable the persistence of the list parameters in the Store, you can pass a custom `storeKey` prop.

```diff
const Dashboard = () => (
<div>
// ...
<ResourceContextProvider value="posts">
- <List disableSyncWithLocation>
+ <List disableSyncWithLocation storeKey="postsListParams">
<SimpleList
primaryText={record => record.title}
secondaryText={record => `${record.views} views`}
tertiaryText={record => new Date(record.published_at).toLocaleDateString()}
/>
</List>
</ResourceContextProvider>
<ResourceContextProvider value="comments">
- <List disableSyncWithLocation>
+ <List disableSyncWithLocation storeKey="commentsListParams">
<SimpleList
primaryText={record => record.title}
secondaryText={record => `${record.views} views`}
tertiaryText={record => new Date(record.published_at).toLocaleDateString()}
/>
</List>
</ResourceContextProvider>
</div>
)
```

Please note that the selection state is not synced in the URL but in a global store using the resource as key. Thus, all lists in the page using the same resource will share the same synced selection state. This is a design choice because if row selection is not tied to a resource, then when a user deletes a record it may remain selected without any ability to unselect it. If you want to allow custom `storeKey`'s for managing selection state, you will have to implement your own `useListController` hook and pass a custom key to the `useRecordSelection` hook. You will then need to implement your own `DeleteButton` and `BulkDeleteButton` to manually unselect rows when deleting records. You can still opt out of all store interactions including selection if you set it to `false`.

Expand Down
58 changes: 57 additions & 1 deletion packages/ra-core/src/controller/list/useListParams.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ describe('useListParams', () => {
});
});

test('should not synchronize parameters with location and store when sync is not enabled', async () => {
it('should not synchronize parameters with location and store when sync is not enabled', async () => {
let location;
let storeValue;
const StoreReader = () => {
Expand Down Expand Up @@ -541,5 +541,61 @@ describe('useListParams', () => {
);
expect(storeValue).toBeUndefined();
});

it('should synchronize parameters with store when sync is not enabled and storeKey is passed', async () => {
let storeValue;
const Component = ({
disableSyncWithLocation = false,
storeKey = undefined,
}) => {
const [{ page }, { setPage }] = useListParams({
resource: 'posts',
disableSyncWithLocation,
storeKey,
});

const handleClick = () => {
setPage(10);
};

return (
<>
<p>page: {page}</p>
<button onClick={handleClick}>update</button>
</>
);
};
const StoreReader = () => {
const [value] = useStore('myListParams');
React.useEffect(() => {
storeValue = value;
}, [value]);
return null;
};

render(
<TestMemoryRouter>
<CoreAdminContext dataProvider={testDataProvider()}>
<Component
disableSyncWithLocation
storeKey="myListParams"
/>
<StoreReader />
</CoreAdminContext>
</TestMemoryRouter>
);

fireEvent.click(screen.getByText('update'));

await screen.findByText('page: 10');

expect(storeValue).toEqual({
filter: {},
order: 'ASC',
page: 10,
perPage: 10,
sort: 'id',
});
});
});
});
23 changes: 7 additions & 16 deletions packages/ra-core/src/controller/list/useListParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const useListParams = ({
perPage = 10,
resource,
sort = defaultSort,
storeKey = `${resource}.listParams`,
storeKey = disableSyncWithLocation ? false : `${resource}.listParams`,
}: ListParamsOptions): [Parameters, Modifiers] => {
const location = useLocation();
const navigate = useNavigate();
Expand All @@ -95,17 +95,12 @@ export const useListParams = ({
);
const tempParams = useRef<ListParams>();
const isMounted = useIsMounted();
const disableSyncWithStore = storeKey === false;

const requestSignature = [
location.search,
resource,
storeKey,
JSON.stringify(
disableSyncWithLocation || disableSyncWithStore
? localParams
: params
),
JSON.stringify(!storeKey ? localParams : params),
JSON.stringify(filterDefaultValues),
JSON.stringify(sort),
perPage,
Expand All @@ -120,10 +115,7 @@ export const useListParams = ({
() =>
getQuery({
queryFromLocation,
params:
disableSyncWithLocation || disableSyncWithStore
? localParams
: params,
params: !storeKey ? localParams : params,
filterDefaultValues,
sort,
perPage,
Expand All @@ -136,10 +128,7 @@ export const useListParams = ({
// store as well so that we don't lose them after a redirection back
// to the list
useEffect(() => {
if (
Object.keys(queryFromLocation).length > 0 &&
!disableSyncWithStore
) {
if (Object.keys(queryFromLocation).length > 0) {
setParams(query);
}
}, [location.search]); // eslint-disable-line
Expand All @@ -160,8 +149,10 @@ export const useListParams = ({
// the side effects were already processed by another changeParams
return;
}
if (disableSyncWithLocation) {
if (disableSyncWithLocation && !storeKey) {
setLocalParams(tempParams.current);
} else if (disableSyncWithLocation && !!storeKey) {
setParams(tempParams.current);
} else {
// the useEffect above will apply the changes to the params in the store
navigate(
Expand Down
44 changes: 44 additions & 0 deletions packages/ra-ui-materialui/src/list/List.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import { TextField } from '../field';
import { SearchInput, TextInput } from '../input';
import { Route } from 'react-router';
import { Link } from 'react-router-dom';
import { ListButton } from '../button';
import { ShowGuesser } from '../detail';
import TopToolbar from '../layout/TopToolbar';

export default { title: 'ra-ui-materialui/list/List' };

Expand Down Expand Up @@ -541,6 +544,47 @@ export const StoreDisabled = () => {
);
};

const BooksWithLocationDisabled = () => (
<List
resource="books"
storeKey="booksParams"
disableSyncWithLocation
sort={{ field: 'year', order: 'ASC' }}
>
<Datagrid>
<TextField source="id" />
<TextField source="title" />
<TextField source="author" />
<TextField source="year" />
</Datagrid>
</List>
);

export const LocationNotSyncWithStore = () => {
const ShowActions = () => (
<TopToolbar>
<ListButton label="ra.action.back" />
</TopToolbar>
);

return (
<TestMemoryRouter initialEntries={['/']}>
<Admin dataProvider={dataProvider}>
<Resource
name="books"
list={<BooksWithLocationDisabled />}
edit={
<ShowGuesser
enableLog={false}
actions={<ShowActions />}
/>
}
/>
</Admin>
</TestMemoryRouter>
);
};

export const ErrorInFetch = () => (
<TestMemoryRouter initialEntries={['/books']}>
<Admin
Expand Down
Loading