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; }