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,