From 787e696eea7a42cc9e6c7c723c0cc4ddeb67bacc Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Tue, 5 Jan 2021 14:12:52 +0100
Subject: [PATCH] List - Optional Sync With Location
---
docs/List.md | 37 +++++++++++++++
examples/simple/src/tags/TagEdit.js | 45 +++++++++++++++----
packages/ra-core/src/controller/ListBase.tsx | 8 +++-
.../src/controller/useListController.ts | 5 +++
.../ra-core/src/controller/useListParams.ts | 26 +++++++----
packages/ra-core/src/core/Resource.tsx | 1 +
.../ra-ui-materialui/src/list/List.spec.tsx | 1 +
packages/ra-ui-materialui/src/types.ts | 1 +
8 files changed, 106 insertions(+), 18 deletions(-)
diff --git a/docs/List.md b/docs/List.md
index 9ba4dfde431..3a1a9e86532 100644
--- a/docs/List.md
+++ b/docs/List.md
@@ -17,6 +17,20 @@ The `` component fetches the list of records from the data provider, and r
Here is the minimal code necessary to display a list of posts using a ``:
+* [`title`](#page-title)
+* [`actions`](#actions)
+* [`exporter`](#exporter)
+* [`bulkActionButtons`](#bulk-action-buttons)
+* [`filters`](#filters) (a React element used to display the filter form)
+* [`filterDefaultValues`](#filter-default-values) (the default values for `alwaysOn` filters)
+* [`perPage`](#records-per-page)
+* [`sort`](#default-sort-field)
+* [`filter`](#permanent-filter) (the permanent filter used in the REST request)
+* [`pagination`](#pagination)
+* [`aside`](#aside-component)
+* [`empty`](#empty-page)
+- [`syncWithLocation`](#synchronize-with-url)
+
```jsx
// in src/posts.js
import * as React from "react";
@@ -729,6 +743,29 @@ const PostList = props => (
The default value for the `component` prop is `Card`.
+## Synchronize With URL
+
+When a List based component (eg: `PostList`) is passed to the `list` prop of a ``, it will automatically synchronize its parameters with the browser URL (using react-router location). However, when used anywhere outside of a ``, it won't synchronize, which can be useful when you have multiple lists on a single page for example.
+
+In order to enable the synchronization with the URL, you can set the `syncWithLocation` prop. For example, adding a `List` to an `Edit` page:
+
+```jsx
+const TagsEdit = (props) => (
+ <>
+
+ // ...
+
+
+
+
+
+
+
+
+ >
+)
+```
+
### CSS API
The `List` component accepts the usual `className` prop but you can override many class names injected to the inner components by React-admin thanks to the `classes` property (as most Material UI components, see their [documentation about it](https://material-ui.com/customization/components/#overriding-styles-with-classes)). This property accepts the following keys:
diff --git a/examples/simple/src/tags/TagEdit.js b/examples/simple/src/tags/TagEdit.js
index 9b622c7c7e3..1adf2848bf3 100644
--- a/examples/simple/src/tags/TagEdit.js
+++ b/examples/simple/src/tags/TagEdit.js
@@ -1,14 +1,43 @@
/* eslint react/jsx-key: off */
import * as React from 'react';
-import { Edit, SimpleForm, TextField, TextInput, required } from 'react-admin';
+import {
+ Edit,
+ SimpleForm,
+ TextField,
+ TextInput,
+ required,
+ List,
+ Datagrid,
+ ResourceContextProvider,
+ EditButton,
+} from 'react-admin';
const TagEdit = props => (
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
-
export default TagEdit;
diff --git a/packages/ra-core/src/controller/ListBase.tsx b/packages/ra-core/src/controller/ListBase.tsx
index 2312d3db58d..c407b9caee1 100644
--- a/packages/ra-core/src/controller/ListBase.tsx
+++ b/packages/ra-core/src/controller/ListBase.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
-import useListController from './useListController';
+import { ReactNode } from 'react';
+import useListController, { ListProps } from './useListController';
import ListContextProvider from './ListContextProvider';
/**
@@ -36,7 +37,10 @@ import ListContextProvider from './ListContextProvider';
*
* );
*/
-const ListBase = ({ children, ...props }) => (
+const ListBase = ({
+ children,
+ ...props
+}: ListProps & { children: ReactNode }) => (
{children}
diff --git a/packages/ra-core/src/controller/useListController.ts b/packages/ra-core/src/controller/useListController.ts
index 38c751d1d36..90fe973d667 100644
--- a/packages/ra-core/src/controller/useListController.ts
+++ b/packages/ra-core/src/controller/useListController.ts
@@ -43,6 +43,9 @@ export interface ListProps {
location?: Location;
path?: string;
resource?: string;
+ // Wether to synchronize the list parameters with the current location (URL search parameters)
+ // This is set to true automatically when a List is used inside a Resource component
+ syncWithLocation: boolean;
[key: string]: any;
}
@@ -118,6 +121,7 @@ const useListController = (
perPage = 10,
filter,
debounce = 500,
+ syncWithLocation,
} = props;
const resource = useResourceContext(props);
@@ -138,6 +142,7 @@ const useListController = (
sort,
perPage,
debounce,
+ syncWithLocation,
});
const [selectedIds, selectionModifiers] = useRecordSelection(resource);
diff --git a/packages/ra-core/src/controller/useListParams.ts b/packages/ra-core/src/controller/useListParams.ts
index e5d634c75cb..4a7f90d7bc8 100644
--- a/packages/ra-core/src/controller/useListParams.ts
+++ b/packages/ra-core/src/controller/useListParams.ts
@@ -27,6 +27,9 @@ interface ListParamsOptions {
// default value for a filter when displayed but not yet set
filterDefaultValues?: FilterPayload;
debounce?: number;
+ // Wether to synchronize the list parameters with the current location (URL search parameters)
+ // This is set to true automatically when a List is used inside a Resource component
+ syncWithLocation: boolean;
}
interface Parameters extends ListParams {
@@ -112,6 +115,7 @@ const useListParams = ({
sort = defaultSort,
perPage = 10,
debounce = 500,
+ syncWithLocation,
}: ListParamsOptions): [Parameters, Modifiers] => {
const dispatch = useDispatch();
const history = useHistory();
@@ -132,7 +136,9 @@ const useListParams = ({
perPage,
];
- const queryFromLocation = parseQueryFromLocation(location);
+ const queryFromLocation = syncWithLocation
+ ? parseQueryFromLocation(location)
+ : {};
const query = useMemo(
() =>
@@ -158,13 +164,17 @@ const useListParams = ({
const changeParams = useCallback(action => {
const newParams = queryReducer(query, action);
- history.push({
- search: `?${stringify({
- ...newParams,
- filter: JSON.stringify(newParams.filter),
- displayedFilters: JSON.stringify(newParams.displayedFilters),
- })}`,
- });
+ if (syncWithLocation) {
+ history.push({
+ search: `?${stringify({
+ ...newParams,
+ filter: JSON.stringify(newParams.filter),
+ displayedFilters: JSON.stringify(
+ newParams.displayedFilters
+ ),
+ })}`,
+ });
+ }
dispatch(changeListParams(resource, newParams));
}, requestSignature); // eslint-disable-line react-hooks/exhaustive-deps
diff --git a/packages/ra-core/src/core/Resource.tsx b/packages/ra-core/src/core/Resource.tsx
index 41f2559d58b..53ddf362c37 100644
--- a/packages/ra-core/src/core/Resource.tsx
+++ b/packages/ra-core/src/core/Resource.tsx
@@ -130,6 +130,7 @@ const ResourceRoutes: FunctionComponent = ({
basePath={basePath}
{...routeProps}
{...resourceData}
+ syncWithLocation
/>
)}
/>
diff --git a/packages/ra-ui-materialui/src/list/List.spec.tsx b/packages/ra-ui-materialui/src/list/List.spec.tsx
index ea20ce30acd..5b0d2afe363 100644
--- a/packages/ra-ui-materialui/src/list/List.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/List.spec.tsx
@@ -22,6 +22,7 @@ describe('
', () => {
history: {} as any,
location: {} as any,
match: (() => {}) as any,
+ syncWithLocation: true,
};
const defaultStateForList = {
diff --git a/packages/ra-ui-materialui/src/types.ts b/packages/ra-ui-materialui/src/types.ts
index 083d8103324..517f8f48171 100644
--- a/packages/ra-ui-materialui/src/types.ts
+++ b/packages/ra-ui-materialui/src/types.ts
@@ -24,6 +24,7 @@ export interface ListProps extends ResourceComponentProps {
pagination?: ReactElement | false;
perPage?: number;
sort?: SortPayload;
+ syncWithLocation: boolean;
title?: string | ReactElement;
}