diff --git a/examples/simple/src/customRouteLayout.js b/examples/simple/src/customRouteLayout.js
index de10cb7da71..b18666178d9 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,18 @@ const CustomRouteLayout = () => {
Found {total} posts !
-
- {ids.map(id => (
- - {data[id].title}
- ))}
-
+
+
+
+
) : null;
};
diff --git a/packages/ra-core/src/controller/details/useCreateContext.tsx b/packages/ra-core/src/controller/details/useCreateContext.tsx
index afef3680630..e8299188d8f 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 { useContext, useMemo } 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
>(
@@ -13,22 +32,59 @@ export const useCreateContext = <
// @ts-ignore
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
+ return useMemo(
+ () =>
+ merge(
+ {},
+ context,
+ props != null ? extractCreateContextProps(props) : {}
+ ),
+ [context, props]
+ );
};
+
+/**
+ * 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,
+}: any) => ({
+ 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..b811e7927b3 100644
--- a/packages/ra-core/src/controller/details/useEditContext.tsx
+++ b/packages/ra-core/src/controller/details/useEditContext.tsx
@@ -1,35 +1,91 @@
-import { useContext } from 'react';
+import { useContext, useMemo } 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
+ return useMemo(
+ () =>
+ merge(
+ {},
+ context,
+ props != null ? extractEditContextProps(props) : {}
+ ),
+ [context, props]
+ );
};
+
+/**
+ * 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,
+}: any) => ({
+ 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..a74e3e46365 100644
--- a/packages/ra-core/src/controller/details/useShowContext.tsx
+++ b/packages/ra-core/src/controller/details/useShowContext.tsx
@@ -1,35 +1,71 @@
-import { useContext } from 'react';
+import { useContext, useMemo } 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
+ return useMemo(
+ () =>
+ merge(
+ {},
+ context,
+ props != null ? extractShowContextProps(props) : {}
+ ),
+ [context, props]
+ );
};
+
+/**
+ * Extract only the show controller props
+ *
+ * @param {Object} props props passed to the useShowContext hook
+ *
+ * @returns {ShowControllerProps} show controller props
+ */
+const extractShowContextProps = ({
+ basePath,
+ record,
+ data,
+ defaultTitle,
+ loaded,
+ loading,
+ resource,
+ version,
+}: any) => ({
+ basePath,
+ // 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,
+ resource,
+ version,
+});
diff --git a/packages/ra-core/src/controller/useListContext.ts b/packages/ra-core/src/controller/useListContext.ts
index 3d059246ff4..4e7b8601ecc 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 { useContext, useMemo } 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,75 @@ 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;
- }
- // @ts-ignore
- return context;
+ // Props take precedence over the context
+ return useMemo(
+ () =>
+ merge(
+ {},
+ context,
+ props != null ? extractListContextProps(props) : {}
+ ),
+ [context, props]
+ );
};
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,
+}) => ({
+ basePath,
+ currentSort,
+ data,
+ defaultTitle,
+ displayedFilters,
+ filterValues,
+ hasCreate,
+ hideFilter,
+ ids,
+ loaded,
+ loading,
+ onSelect,
+ onToggleItem,
+ onUnselectItems,
+ page,
+ perPage,
+ resource,
+ selectedIds,
+ setFilters,
+ setPage,
+ setPerPage,
+ setSort,
+ showFilter,
+ total,
+});
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);