diff --git a/packages/ra-core/src/controller/field/ReferenceFieldBase.stories.tsx b/packages/ra-core/src/controller/field/ReferenceFieldBase.stories.tsx
index fc2a64f798a..5ef4bdafd4d 100644
--- a/packages/ra-core/src/controller/field/ReferenceFieldBase.stories.tsx
+++ b/packages/ra-core/src/controller/field/ReferenceFieldBase.stories.tsx
@@ -11,7 +11,7 @@ import { useReferenceFieldContext } from './ReferenceFieldContext';
import { DataProvider } from '../../types';
export default {
- title: 'ra-core/fields/ReferenceFieldBase',
+ title: 'ra-core/controller/field/ReferenceFieldBase',
excludeStories: ['dataProviderWithAuthors'],
};
@@ -359,7 +359,7 @@ const MyReferenceField = (props: { children: React.ReactNode }) => {
}
if (context.error) {
- return
{context.error}
;
+ return {context.error.toString()}
;
}
return props.children;
};
diff --git a/packages/ra-core/src/controller/field/ReferenceManyCountBase.stories.tsx b/packages/ra-core/src/controller/field/ReferenceManyCountBase.stories.tsx
new file mode 100644
index 00000000000..9b188ed9c92
--- /dev/null
+++ b/packages/ra-core/src/controller/field/ReferenceManyCountBase.stories.tsx
@@ -0,0 +1,143 @@
+import * as React from 'react';
+import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
+import { RecordContextProvider } from '../record';
+import { DataProviderContext } from '../../dataProvider';
+import { ResourceContextProvider } from '../../core';
+import { TestMemoryRouter } from '../../routing';
+import { ReferenceManyCountBase } from './ReferenceManyCountBase';
+
+export default {
+ title: 'ra-core/controller/field/ReferenceManyCountBase',
+ excludeStories: ['Wrapper'],
+};
+
+const post = {
+ id: 1,
+ title: 'Lorem Ipsum',
+};
+const comments = [
+ { id: 1, post_id: 1, is_published: true },
+ { id: 2, post_id: 1, is_published: true },
+ { id: 3, post_id: 1, is_published: false },
+ { id: 4, post_id: 2, is_published: true },
+ { id: 5, post_id: 2, is_published: false },
+];
+
+export const Wrapper = ({ dataProvider, children }) => (
+
+
+
+
+
+ {children}
+
+
+
+
+
+);
+
+export const Basic = () => (
+
+ Promise.resolve({
+ data: [comments.filter(c => c.post_id === 1)[0]],
+ total: comments.filter(c => c.post_id === 1).length,
+ }),
+ }}
+ >
+
+
+);
+
+export const LoadingState = () => (
+ new Promise(() => {}) }}>
+
+
+);
+
+export const ErrorState = () => (
+ Promise.reject(new Error('problem')),
+ }}
+ >
+
+
+);
+
+export const Filter = () => (
+
+ Promise.resolve({
+ data: comments
+ .filter(c => c.post_id === 1)
+ .filter(post =>
+ Object.keys(params.filter).every(
+ key => post[key] === params.filter[key]
+ )
+ ),
+ total: comments
+ .filter(c => c.post_id === 1)
+ .filter(post =>
+ Object.keys(params.filter).every(
+ key => post[key] === params.filter[key]
+ )
+ ).length,
+ }),
+ }}
+ >
+
+
+);
+
+export const Slow = () => (
+
+ new Promise(resolve =>
+ setTimeout(
+ () =>
+ resolve({
+ data: [
+ comments.filter(c => c.post_id === 1)[0],
+ ],
+ total: comments.filter(c => c.post_id === 1)
+ .length,
+ }),
+ 2000
+ )
+ ),
+ }}
+ >
+
+
+);
diff --git a/packages/ra-core/src/controller/field/ReferenceManyCountBase.tsx b/packages/ra-core/src/controller/field/ReferenceManyCountBase.tsx
new file mode 100644
index 00000000000..070c1fefa38
--- /dev/null
+++ b/packages/ra-core/src/controller/field/ReferenceManyCountBase.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import {
+ useReferenceManyFieldController,
+ type UseReferenceManyFieldControllerParams,
+} from './useReferenceManyFieldController';
+import { useTimeout } from '../../util/hooks';
+
+/**
+ * Fetch and render the number of records related to the current one
+ *
+ * Relies on dataProvider.getManyReference() returning a total property
+ *
+ * @example // Display the number of comments for the current post
+ *
+ *
+ * @example // Display the number of published comments for the current post
+ *
+ */
+export const ReferenceManyCountBase = (props: ReferenceManyCountBaseProps) => {
+ const { loading = null, error = null, timeout = 1000, ...rest } = props;
+ const oneSecondHasPassed = useTimeout(timeout);
+
+ const {
+ isPending,
+ error: fetchError,
+ total,
+ } = useReferenceManyFieldController({
+ ...rest,
+ page: 1,
+ perPage: 1,
+ });
+
+ return (
+ <>
+ {isPending
+ ? oneSecondHasPassed
+ ? loading
+ : null
+ : fetchError
+ ? error
+ : total}
+ >
+ );
+};
+
+export interface ReferenceManyCountBaseProps
+ extends UseReferenceManyFieldControllerParams {
+ timeout?: number;
+ loading?: React.ReactNode;
+ error?: React.ReactNode;
+}
diff --git a/packages/ra-core/src/controller/field/index.ts b/packages/ra-core/src/controller/field/index.ts
index 1b22b1b4d0e..d03d0e239fd 100644
--- a/packages/ra-core/src/controller/field/index.ts
+++ b/packages/ra-core/src/controller/field/index.ts
@@ -1,5 +1,6 @@
export * from './ReferenceFieldBase';
export * from './ReferenceFieldContext';
+export * from './ReferenceManyCountBase';
export * from './useReferenceArrayFieldController';
export * from './useReferenceFieldController';
export * from './useReferenceManyFieldController';
diff --git a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts
index e74a3a3ceef..cb482784fb1 100644
--- a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts
+++ b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts
@@ -13,6 +13,7 @@ import usePaginationState from '../usePaginationState';
import { useRecordSelection } from '../list/useRecordSelection';
import useSortState from '../useSortState';
import { useResourceContext } from '../../core';
+import { useRecordContext } from '../record';
/**
* Fetch reference records, and return them when available
@@ -55,7 +56,6 @@ export const useReferenceManyFieldController = <
const {
debounce = 500,
reference,
- record,
target,
filter = defaultFilter,
source = 'id',
@@ -68,6 +68,7 @@ export const useReferenceManyFieldController = <
>,
} = props;
const notify = useNotify();
+ const record = useRecordContext(props);
const resource = useResourceContext(props);
const dataProvider = useDataProvider();
const queryClient = useQueryClient();
diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx
index 8f576fabe8a..ce35df3a64c 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx
@@ -1,9 +1,8 @@
import React from 'react';
import {
- useReferenceManyFieldController,
useRecordContext,
- useTimeout,
useCreatePath,
+ ReferenceManyCountBase,
SortPayload,
RaRecord,
} from 'ra-core';
@@ -15,6 +14,7 @@ import {
useThemeProps,
} from '@mui/material/styles';
import ErrorIcon from '@mui/icons-material/Error';
+import get from 'lodash/get';
import { FieldProps } from './types';
import { sanitizeFieldRestProps } from './sanitizeFieldRestProps';
@@ -51,39 +51,20 @@ export const ReferenceManyCount = (
link,
resource,
source = 'id',
- timeout = 1000,
...rest
} = props;
const record = useRecordContext(props);
- const oneSecondHasPassed = useTimeout(timeout);
const createPath = useCreatePath();
- const { isPending, error, total } =
- useReferenceManyFieldController({
- filter,
- sort,
- page: 1,
- perPage: 1,
- record,
- reference,
- // @ts-ignore remove when #8491 is released
- resource,
- source,
- target,
- });
-
- const body = isPending ? (
- oneSecondHasPassed ? (
-
- ) : (
- ''
- )
- ) : error ? (
-
- ) : (
- total
+ const body = (
+ }
+ error={
+
+ }
+ />
);
-
return (
(
}),
search: `filter=${JSON.stringify({
...(filter || {}),
- [target]: record[source],
+ [target]: get(record, source),
})}`,
}}
onClick={e => e.stopPropagation()}
diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx
index 1413ca20515..ee92d6c8dc1 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx
@@ -3,7 +3,6 @@ import {
useReferenceManyFieldController,
ListContextProvider,
ResourceContextProvider,
- useRecordContext,
RaRecord,
UseReferenceManyFieldControllerParams,
} from 'ra-core';
@@ -69,6 +68,7 @@ export const ReferenceManyField = <
page = 1,
pagination = null,
perPage = 25,
+ record,
reference,
resource,
sort = defaultSort,
@@ -77,7 +77,6 @@ export const ReferenceManyField = <
target,
queryOptions,
} = props;
- const record = useRecordContext(props);
const controllerProps = useReferenceManyFieldController<
RecordType,