From 38a5b5fd4e7d6ff77e8accc4566fe80768bf9631 Mon Sep 17 00:00:00 2001 From: Luwangel Date: Wed, 20 Jan 2021 14:34:52 +0100 Subject: [PATCH 1/8] Change the use list, create, edit and show context strategy by allowing props overrides --- .../controller/details/useCreateContext.tsx | 86 ++++++++++++---- .../src/controller/details/useEditContext.tsx | 97 ++++++++++++++----- .../src/controller/details/useShowContext.tsx | 73 +++++++++----- .../ra-core/src/controller/useListContext.ts | 91 +++++++++++++---- 4 files changed, 264 insertions(+), 83 deletions(-) diff --git a/packages/ra-core/src/controller/details/useCreateContext.tsx b/packages/ra-core/src/controller/details/useCreateContext.tsx index afef3680630..36cf246d8f8 100644 --- a/packages/ra-core/src/controller/details/useCreateContext.tsx +++ b/packages/ra-core/src/controller/details/useCreateContext.tsx @@ -1,8 +1,27 @@ import { useContext } from 'react'; +import merge from 'lodash/merge'; + import { Record } from '../../types'; import { CreateContext } from './CreateContext'; import { CreateControllerProps } from './useCreateController'; +/** + * Hook to read the create controller props from the CreateContext. + * + * Mostly used within a (e.g. as a descendent of ). + * + * But you can also use it without a . In this case, it is up to you + * to pass all the necessary props. + * + * The given props will take precedence over context values. + * + * @typedef {Object} CreateControllerProps + * + * @returns {CreateControllerProps} create controller props + * + * @see useCreateController for how it is filled + * + */ export const useCreateContext = < RecordType extends Omit = Omit >( @@ -14,21 +33,54 @@ export const useCreateContext = < CreateContext ); - if (!context.resource) { - /** - * The element isn't inside a - * To avoid breakage in that case, fallback to props - * - * @deprecated - to be removed in 4.0 - */ - if (process.env.NODE_ENV !== 'production') { - console.log( - "Create components must be used inside a . Relying on props rather than context to get Create data and callbacks is deprecated and won't be supported in the next major version of react-admin." - ); - } - - return props; - } - - return context; + // Props take precedence over the context + // @ts-ignore + return props != null + ? merge({}, context, extractCreateContextProps(props)) + : context; }; + +/** + * Extract only the create controller props + * + * @param {Object} props props passed to the useCreateContext hook + * + * @returns {CreateControllerProps} create controller props + */ +const extractCreateContextProps = ({ + basePath, + record, + defaultTitle, + onFailureRef, + onSuccessRef, + transformRef, + loaded, + loading, + redirect, + setOnFailure, + setOnSuccess, + setTransform, + resource, + save, + saving, + successMessage, + version, +}): CreateControllerProps => ({ + basePath, + record, + defaultTitle, + onFailureRef, + onSuccessRef, + transformRef, + loaded, + loading, + redirect, + setOnFailure, + setOnSuccess, + setTransform, + resource, + save, + saving, + successMessage, + version, +}); diff --git a/packages/ra-core/src/controller/details/useEditContext.tsx b/packages/ra-core/src/controller/details/useEditContext.tsx index 73493c7c440..a5625b550ee 100644 --- a/packages/ra-core/src/controller/details/useEditContext.tsx +++ b/packages/ra-core/src/controller/details/useEditContext.tsx @@ -1,35 +1,86 @@ import { useContext } from 'react'; +import merge from 'lodash/merge'; + import { Record } from '../../types'; import { EditContext } from './EditContext'; import { EditControllerProps } from './useEditController'; +/** + * Hook to read the edit controller props from the CreateContext. + * + * Mostly used within a (e.g. as a descendent of ). + * + * But you can also use it without a . In this case, it is up to you + * to pass all the necessary props. + * + * The given props will take precedence over context values. + * + * @typedef {Object} EditControllerProps + * + * @returns {EditControllerProps} edit controller props + * + * @see useEditController for how it is filled + * + */ export const useEditContext = ( props?: Partial> ): Partial> => { - // Can't find a way to specify the RecordType when CreateContext is declared + // Can't find a way to specify the RecordType when EditContext is declared // @ts-ignore const context = useContext>(EditContext); - if (!context.resource) { - /** - * The element isn't inside a - * To avoid breakage in that case, fallback to props - * - * @deprecated - to be removed in 4.0 - */ - if (process.env.NODE_ENV !== 'production') { - console.log( - "Edit components must be used inside a . Relying on props rather than context to get Edit data and callbacks is deprecated and won't be supported in the next major version of react-admin." - ); - } - // Necessary for actions (EditActions) which expect a data prop containing the record - // @deprecated - to be removed in 4.0d - return { - ...props, - record: props.record || props.data, - data: props.record || props.data, - }; - } - - return context; + // Props take precedence over the context + // @ts-ignore + return props != null + ? merge({}, context, extractEditContextProps(props)) + : context; }; + +/** + * Extract only the edit controller props + * + * @param {Object} props props passed to the useEditContext hook + * + * @returns {EditControllerProps} edit controller props + */ +const extractEditContextProps = ({ + basePath, + data, + record, + defaultTitle, + onFailureRef, + onSuccessRef, + transformRef, + loaded, + loading, + redirect, + setOnFailure, + setOnSuccess, + setTransform, + resource, + save, + saving, + successMessage, + version, +}): CreateControllerProps => ({ + basePath, + // Necessary for actions (EditActions) which expect a data prop containing the record + // @deprecated - to be removed in 4.0d + data: record || data, + record: record || data, + defaultTitle, + onFailureRef, + onSuccessRef, + transformRef, + loaded, + loading, + redirect, + setOnFailure, + setOnSuccess, + setTransform, + resource, + save, + saving, + successMessage, + version, +}); diff --git a/packages/ra-core/src/controller/details/useShowContext.tsx b/packages/ra-core/src/controller/details/useShowContext.tsx index cbd5badfaef..1ff1ed1db07 100644 --- a/packages/ra-core/src/controller/details/useShowContext.tsx +++ b/packages/ra-core/src/controller/details/useShowContext.tsx @@ -1,35 +1,62 @@ import { useContext } from 'react'; +import merge from 'lodash/merge'; + import { Record } from '../../types'; import { ShowContext } from './ShowContext'; import { ShowControllerProps } from './useShowController'; +/** + * Hook to read the show controller props from the ShowContext. + * + * Mostly used within a (e.g. as a descendent of ). + * + * But you can also use it without a . In this case, it is up to you + * to pass all the necessary props. + * + * The given props will take precedence over context values. + * + * @typedef {Object} ShowControllerProps + * + * @returns {ShowControllerProps} create controller props + * + * @see useShowController for how it is filled + * + */ export const useShowContext = ( props?: Partial> ): Partial> => { - // Can't find a way to specify the RecordType when CreateContext is declared + // Can't find a way to specify the RecordType when ShowContext is declared // @ts-ignore const context = useContext>(ShowContext); - if (!context.resource) { - /** - * The element isn't inside a - * To avoid breakage in that case, fallback to props - * - * @deprecated - to be removed in 4.0 - */ - if (process.env.NODE_ENV !== 'production') { - console.log( - "Show components must be used inside a . Relying on props rather than context to get Show data and callbacks is deprecated and won't be supported in the next major version of react-admin." - ); - } - // Necessary for actions (EditActions) which expect a data prop containing the record - // @deprecated - to be removed in 4.0d - return { - ...props, - record: props.record || props.data, - data: props.record || props.data, - }; - } - - return context; + // Props take precedence over the context + // @ts-ignore + return props != null + ? merge({}, context, extractShowContextProps(props)) + : context; }; + +/** + * Extract only the show controller props + * + * @param {Object} props props passed to the useShowContext hook + * + * @returns {ShowControllerProps} show controller props + */ +const extractShowContextProps = ({ + basePath, + record, + defaultTitle, + loaded, + loading, + resource, + version, +}): ShowControllerProps => ({ + basePath, + record, + defaultTitle, + loaded, + loading, + resource, + version, +}); diff --git a/packages/ra-core/src/controller/useListContext.ts b/packages/ra-core/src/controller/useListContext.ts index 3d059246ff4..4478b241ece 100644 --- a/packages/ra-core/src/controller/useListContext.ts +++ b/packages/ra-core/src/controller/useListContext.ts @@ -1,4 +1,5 @@ import { useContext } from 'react'; +import merge from 'lodash/merge'; import ListContext from './ListContext'; import { ListControllerProps } from './useListController'; @@ -7,9 +8,14 @@ import { Record } from '../types'; /** * Hook to read the list controller props from the ListContext. * - * Must be used within a (e.g. as a descendent of + * Mostly used within a (e.g. as a descendent of * or ). * + * But you can also use it without a . In this case, it is up to you + * to pass all the necessary props (see the list below). + * + * The given props will take precedence over context values. + * * @typedef {Object} ListControllerProps * @prop {Object} data an id-based dictionary of the list data, e.g. { 123: { id: 123, title: 'hello world' }, 456: { ... } } * @prop {Array} ids an array listing the ids of the records in the list, e.g. [123, 456, ...] @@ -91,26 +97,71 @@ const useListContext = ( props?: any ): ListControllerProps => { const context = useContext(ListContext); - if (!context.resource) { - /** - * The element isn't inside a - * - * This may only happen when using Datagrid / SimpleList / SingleFieldList components - * outside of a List / ReferenceManyField / ReferenceArrayField - - * which isn't documented but tolerated. - * To avoid breakage in that case, fallback to props - * - * @deprecated - to be removed in 4.0 - */ - if (process.env.NODE_ENV !== 'production') { - console.log( - "List components must be used inside a . Relying on props rather than context to get List data and callbacks is deprecated and won't be supported in the next major version of react-admin." - ); - } - return props; - } + + // Props take precedence over the context // @ts-ignore - return context; + return props != null + ? merge({}, context, extractListContextProps(props)) + : context; }; export default useListContext; + +/** + * Extract only the list controller props + * + * @param {Object} props Props passed to the useListContext hook + * + * @returns {ListControllerProps} List controller props + */ +const extractListContextProps = ({ + basePath, + currentSort, + data, + defaultTitle, + displayedFilters, + filterValues, + hasCreate, + hideFilter, + ids, + loaded, + loading, + onSelect, + onToggleItem, + onUnselectItems, + page, + perPage, + resource, + selectedIds, + setFilters, + setPage, + setPerPage, + setSort, + showFilter, + total, +}): ListControllerProps => ({ + basePath, + currentSort, + data, + defaultTitle, + displayedFilters, + filterValues, + hasCreate, + hideFilter, + ids, + loaded, + loading, + onSelect, + onToggleItem, + onUnselectItems, + page, + perPage, + resource, + selectedIds, + setFilters, + setPage, + setPerPage, + setSort, + showFilter, + total, +}); From 169bad4a77c9f7b7f40f8b0225f9b0313a272349 Mon Sep 17 00:00:00 2001 From: Luwangel Date: Wed, 20 Jan 2021 15:20:12 +0100 Subject: [PATCH 2/8] Add an example of the usage of a Datagrid ouside of a list context --- examples/simple/src/customRouteLayout.js | 40 +++++++++++++++---- .../src/list/datagrid/Datagrid.tsx | 37 +++++++++++++++++ 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/examples/simple/src/customRouteLayout.js b/examples/simple/src/customRouteLayout.js index de10cb7da71..3c61f8e439d 100644 --- a/examples/simple/src/customRouteLayout.js +++ b/examples/simple/src/customRouteLayout.js @@ -1,12 +1,21 @@ import * as React from 'react'; -import { useGetList, useAuthenticated, Title } from 'react-admin'; +import { + useGetList, + useAuthenticated, + Datagrid, + TextField, + Title, +} from 'react-admin'; + +const currentSort = { field: 'published_at', order: 'DESC' }; const CustomRouteLayout = () => { useAuthenticated(); + const { ids, data, total, loaded } = useGetList( 'posts', { page: 1, perPage: 10 }, - { field: 'published_at', order: 'DESC' } + currentSort ); return loaded ? ( @@ -16,11 +25,28 @@ const CustomRouteLayout = () => {

Found {total} posts !

-
    - {ids.map(id => ( -
  • {data[id].title}
  • - ))} -
+ { + console.log('set sort'); + }} + onSelect={() => { + console.log('on select'); + }} + onToggleItem={() => { + console.log('on toggle item'); + }} + > + + + ) : null; }; diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx index be0ac217b5a..05f506a4765 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx @@ -62,6 +62,43 @@ import { ClassesOverride } from '../../types'; * * * + * + * + * @example Usage it outside of a or a . + * + * const currentSort = { field: 'published_at', order: 'DESC' }; + * + * export const MyCustomList = (props) => { + * const { ids, data, total, loaded } = useGetList( + * 'posts', + * { page: 1, perPage: 10 }, + * currentSort + * ); + * + * return ( + * { + * console.log('set sort'); + * }} + * onSelect={() => { + * console.log('on select'); + * }} + * onToggleItem={() => { + * console.log('on toggle item'); + * }} + * > + * + * + * + * ); + * } */ const Datagrid: FC = React.forwardRef((props, ref) => { const classes = useDatagridStyles(props); From 823f2465ac9c005b063f9fe5ed1021787f9d7e6a Mon Sep 17 00:00:00 2001 From: Luwangel Date: Wed, 20 Jan 2021 15:25:09 +0100 Subject: [PATCH 3/8] Prevent a warning with the syncWithLocation prop in ListView --- packages/ra-ui-materialui/src/list/ListView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ra-ui-materialui/src/list/ListView.tsx b/packages/ra-ui-materialui/src/list/ListView.tsx index a14947c4eb0..92ded657777 100644 --- a/packages/ra-ui-materialui/src/list/ListView.tsx +++ b/packages/ra-ui-materialui/src/list/ListView.tsx @@ -250,6 +250,7 @@ const sanitizeRestProps: ( showFilter = null, syncWithLocation = null, sort = null, + syncWithLocation = null, total = null, ...rest }) => rest; From 1e98ccbca18c97a8a20997da675ef13c42dffba4 Mon Sep 17 00:00:00 2001 From: Luwangel Date: Wed, 20 Jan 2021 15:45:58 +0100 Subject: [PATCH 4/8] Remove optional parameters in the customRouteLayout in the simple example --- examples/simple/src/customRouteLayout.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/examples/simple/src/customRouteLayout.js b/examples/simple/src/customRouteLayout.js index 3c61f8e439d..b18666178d9 100644 --- a/examples/simple/src/customRouteLayout.js +++ b/examples/simple/src/customRouteLayout.js @@ -33,16 +33,6 @@ const CustomRouteLayout = () => { selectedIds={[]} loaded={loaded} total={total} - // Optional parameters below - setSort={() => { - console.log('set sort'); - }} - onSelect={() => { - console.log('on select'); - }} - onToggleItem={() => { - console.log('on toggle item'); - }} > From 0907cc287dc377cbed6401d8ed9bfda036d37cbf Mon Sep 17 00:00:00 2001 From: Luwangel Date: Wed, 20 Jan 2021 16:39:42 +0100 Subject: [PATCH 5/8] Remove types in internal methods --- packages/ra-core/src/controller/details/useCreateContext.tsx | 2 +- packages/ra-core/src/controller/details/useEditContext.tsx | 2 +- packages/ra-core/src/controller/details/useShowContext.tsx | 2 +- packages/ra-core/src/controller/useListContext.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ra-core/src/controller/details/useCreateContext.tsx b/packages/ra-core/src/controller/details/useCreateContext.tsx index 36cf246d8f8..130325e7e1d 100644 --- a/packages/ra-core/src/controller/details/useCreateContext.tsx +++ b/packages/ra-core/src/controller/details/useCreateContext.tsx @@ -65,7 +65,7 @@ const extractCreateContextProps = ({ saving, successMessage, version, -}): CreateControllerProps => ({ +}) => ({ basePath, record, defaultTitle, diff --git a/packages/ra-core/src/controller/details/useEditContext.tsx b/packages/ra-core/src/controller/details/useEditContext.tsx index a5625b550ee..f8bc8096423 100644 --- a/packages/ra-core/src/controller/details/useEditContext.tsx +++ b/packages/ra-core/src/controller/details/useEditContext.tsx @@ -62,7 +62,7 @@ const extractEditContextProps = ({ saving, successMessage, version, -}): CreateControllerProps => ({ +}) => ({ basePath, // Necessary for actions (EditActions) which expect a data prop containing the record // @deprecated - to be removed in 4.0d diff --git a/packages/ra-core/src/controller/details/useShowContext.tsx b/packages/ra-core/src/controller/details/useShowContext.tsx index 1ff1ed1db07..1ebf02b5170 100644 --- a/packages/ra-core/src/controller/details/useShowContext.tsx +++ b/packages/ra-core/src/controller/details/useShowContext.tsx @@ -51,7 +51,7 @@ const extractShowContextProps = ({ loading, resource, version, -}): ShowControllerProps => ({ +}) => ({ basePath, record, defaultTitle, diff --git a/packages/ra-core/src/controller/useListContext.ts b/packages/ra-core/src/controller/useListContext.ts index 4478b241ece..84df8e87eee 100644 --- a/packages/ra-core/src/controller/useListContext.ts +++ b/packages/ra-core/src/controller/useListContext.ts @@ -139,7 +139,7 @@ const extractListContextProps = ({ setSort, showFilter, total, -}): ListControllerProps => ({ +}) => ({ basePath, currentSort, data, From cf99185e9118f58050a3a49f89754671790f4dd0 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Sat, 13 Feb 2021 09:49:06 +0100 Subject: [PATCH 6/8] Add memoization --- .../controller/details/useCreateContext.tsx | 18 +++++++++++------- .../src/controller/details/useEditContext.tsx | 17 +++++++++++------ .../src/controller/details/useShowContext.tsx | 17 +++++++++++------ .../ra-core/src/controller/useListContext.ts | 16 ++++++++++------ 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/packages/ra-core/src/controller/details/useCreateContext.tsx b/packages/ra-core/src/controller/details/useCreateContext.tsx index 130325e7e1d..e8299188d8f 100644 --- a/packages/ra-core/src/controller/details/useCreateContext.tsx +++ b/packages/ra-core/src/controller/details/useCreateContext.tsx @@ -1,4 +1,4 @@ -import { useContext } from 'react'; +import { useContext, useMemo } from 'react'; import merge from 'lodash/merge'; import { Record } from '../../types'; @@ -32,12 +32,16 @@ export const useCreateContext = < // @ts-ignore CreateContext ); - // Props take precedence over the context - // @ts-ignore - return props != null - ? merge({}, context, extractCreateContextProps(props)) - : context; + return useMemo( + () => + merge( + {}, + context, + props != null ? extractCreateContextProps(props) : {} + ), + [context, props] + ); }; /** @@ -65,7 +69,7 @@ const extractCreateContextProps = ({ saving, successMessage, version, -}) => ({ +}: any) => ({ basePath, record, defaultTitle, diff --git a/packages/ra-core/src/controller/details/useEditContext.tsx b/packages/ra-core/src/controller/details/useEditContext.tsx index f8bc8096423..b811e7927b3 100644 --- a/packages/ra-core/src/controller/details/useEditContext.tsx +++ b/packages/ra-core/src/controller/details/useEditContext.tsx @@ -1,4 +1,4 @@ -import { useContext } from 'react'; +import { useContext, useMemo } from 'react'; import merge from 'lodash/merge'; import { Record } from '../../types'; @@ -30,10 +30,15 @@ export const useEditContext = ( const context = useContext>(EditContext); // Props take precedence over the context - // @ts-ignore - return props != null - ? merge({}, context, extractEditContextProps(props)) - : context; + return useMemo( + () => + merge( + {}, + context, + props != null ? extractEditContextProps(props) : {} + ), + [context, props] + ); }; /** @@ -62,7 +67,7 @@ const extractEditContextProps = ({ saving, successMessage, version, -}) => ({ +}: any) => ({ basePath, // Necessary for actions (EditActions) which expect a data prop containing the record // @deprecated - to be removed in 4.0d diff --git a/packages/ra-core/src/controller/details/useShowContext.tsx b/packages/ra-core/src/controller/details/useShowContext.tsx index 1ebf02b5170..fe615c70abc 100644 --- a/packages/ra-core/src/controller/details/useShowContext.tsx +++ b/packages/ra-core/src/controller/details/useShowContext.tsx @@ -1,4 +1,4 @@ -import { useContext } from 'react'; +import { useContext, useMemo } from 'react'; import merge from 'lodash/merge'; import { Record } from '../../types'; @@ -30,10 +30,15 @@ export const useShowContext = ( const context = useContext>(ShowContext); // Props take precedence over the context - // @ts-ignore - return props != null - ? merge({}, context, extractShowContextProps(props)) - : context; + return useMemo( + () => + merge( + {}, + context, + props != null ? extractShowContextProps(props) : {} + ), + [context, props] + ); }; /** @@ -51,7 +56,7 @@ const extractShowContextProps = ({ loading, resource, version, -}) => ({ +}: any) => ({ basePath, record, defaultTitle, diff --git a/packages/ra-core/src/controller/useListContext.ts b/packages/ra-core/src/controller/useListContext.ts index 84df8e87eee..4e7b8601ecc 100644 --- a/packages/ra-core/src/controller/useListContext.ts +++ b/packages/ra-core/src/controller/useListContext.ts @@ -1,4 +1,4 @@ -import { useContext } from 'react'; +import { useContext, useMemo } from 'react'; import merge from 'lodash/merge'; import ListContext from './ListContext'; @@ -97,12 +97,16 @@ const useListContext = ( props?: any ): ListControllerProps => { const context = useContext(ListContext); - // Props take precedence over the context - // @ts-ignore - return props != null - ? merge({}, context, extractListContextProps(props)) - : context; + return useMemo( + () => + merge( + {}, + context, + props != null ? extractListContextProps(props) : {} + ), + [context, props] + ); }; export default useListContext; From 557c26ccb348e7f6c8ad566036e4cce29b8fac88 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Sat, 13 Feb 2021 09:55:09 +0100 Subject: [PATCH 7/8] Fix rebase --- packages/ra-ui-materialui/src/list/ListView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ra-ui-materialui/src/list/ListView.tsx b/packages/ra-ui-materialui/src/list/ListView.tsx index 92ded657777..a14947c4eb0 100644 --- a/packages/ra-ui-materialui/src/list/ListView.tsx +++ b/packages/ra-ui-materialui/src/list/ListView.tsx @@ -250,7 +250,6 @@ const sanitizeRestProps: ( showFilter = null, syncWithLocation = null, sort = null, - syncWithLocation = null, total = null, ...rest }) => rest; From 74d3c5c924a8870904febc655d3851acaeb42a57 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Sat, 13 Feb 2021 17:56:43 +0100 Subject: [PATCH 8/8] restore BC measure --- packages/ra-core/src/controller/details/useShowContext.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ra-core/src/controller/details/useShowContext.tsx b/packages/ra-core/src/controller/details/useShowContext.tsx index fe615c70abc..a74e3e46365 100644 --- a/packages/ra-core/src/controller/details/useShowContext.tsx +++ b/packages/ra-core/src/controller/details/useShowContext.tsx @@ -51,6 +51,7 @@ export const useShowContext = ( const extractShowContextProps = ({ basePath, record, + data, defaultTitle, loaded, loading, @@ -58,7 +59,10 @@ const extractShowContextProps = ({ version, }: any) => ({ basePath, - record, + // Necessary for actions (EditActions) which expect a data prop containing the record + // @deprecated - to be removed in 4.0d + record: record || data, + data: record || data, defaultTitle, loaded, loading,