Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
};

Expand Down Expand Up @@ -359,7 +359,7 @@ const MyReferenceField = (props: { children: React.ReactNode }) => {
}

if (context.error) {
return <p style={{ color: 'red' }}>{context.error}</p>;
return <p style={{ color: 'red' }}>{context.error.toString()}</p>;
}
return props.children;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }) => (
<TestMemoryRouter>
<DataProviderContext.Provider value={dataProvider}>
<QueryClientProvider
client={
new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
}
>
<ResourceContextProvider value="posts">
<RecordContextProvider value={post}>
{children}
</RecordContextProvider>
</ResourceContextProvider>
</QueryClientProvider>
</DataProviderContext.Provider>
</TestMemoryRouter>
);

export const Basic = () => (
<Wrapper
dataProvider={{
getManyReference: () =>
Promise.resolve({
data: [comments.filter(c => c.post_id === 1)[0]],
total: comments.filter(c => c.post_id === 1).length,
}),
}}
>
<ReferenceManyCountBase reference="comments" target="post_id" />
</Wrapper>
);

export const LoadingState = () => (
<Wrapper dataProvider={{ getManyReference: () => new Promise(() => {}) }}>
<ReferenceManyCountBase
reference="comments"
target="post_id"
loading="loading..."
/>
</Wrapper>
);

export const ErrorState = () => (
<Wrapper
dataProvider={{
getManyReference: () => Promise.reject(new Error('problem')),
}}
>
<ReferenceManyCountBase
reference="comments"
target="post_id"
error="Error!"
/>
</Wrapper>
);

export const Filter = () => (
<Wrapper
dataProvider={{
getManyReference: (resource, params) =>
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,
}),
}}
>
<ReferenceManyCountBase
reference="comments"
target="post_id"
filter={{ is_published: true }}
/>
</Wrapper>
);

export const Slow = () => (
<Wrapper
dataProvider={{
getManyReference: () =>
new Promise(resolve =>
setTimeout(
() =>
resolve({
data: [
comments.filter(c => c.post_id === 1)[0],
],
total: comments.filter(c => c.post_id === 1)
.length,
}),
2000
)
),
}}
>
<ReferenceManyCountBase
reference="comments"
target="post_id"
loading="Loading..."
/>
</Wrapper>
);
51 changes: 51 additions & 0 deletions packages/ra-core/src/controller/field/ReferenceManyCountBase.tsx
Original file line number Diff line number Diff line change
@@ -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
* <ReferenceManyCountBase reference="comments" target="post_id" />
*
* @example // Display the number of published comments for the current post
* <ReferenceManyCountBase reference="comments" target="post_id" filter={{ is_published: true }} />
*/
export const ReferenceManyCountBase = (props: ReferenceManyCountBaseProps) => {
const { loading = null, error = null, timeout = 1000, ...rest } = props;
const oneSecondHasPassed = useTimeout(timeout);

const {
isPending,
error: fetchError,
total,
} = useReferenceManyFieldController<any, any>({
...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;
}
1 change: 1 addition & 0 deletions packages/ra-core/src/controller/field/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './ReferenceFieldBase';
export * from './ReferenceFieldContext';
export * from './ReferenceManyCountBase';
export * from './useReferenceArrayFieldController';
export * from './useReferenceFieldController';
export * from './useReferenceManyFieldController';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,7 +56,6 @@ export const useReferenceManyFieldController = <
const {
debounce = 500,
reference,
record,
target,
filter = defaultFilter,
source = 'id',
Expand All @@ -68,6 +68,7 @@ export const useReferenceManyFieldController = <
>,
} = props;
const notify = useNotify();
const record = useRecordContext(props);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done by callers of the hook. This logic should better be in ra-core.

const resource = useResourceContext(props);
const dataProvider = useDataProvider();
const queryClient = useQueryClient();
Expand Down
41 changes: 11 additions & 30 deletions packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react';
import {
useReferenceManyFieldController,
useRecordContext,
useTimeout,
useCreatePath,
ReferenceManyCountBase,
SortPayload,
RaRecord,
} from 'ra-core';
Expand All @@ -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';
Expand Down Expand Up @@ -51,39 +51,20 @@ export const ReferenceManyCount = <RecordType extends RaRecord = RaRecord>(
link,
resource,
source = 'id',
timeout = 1000,
...rest
} = props;
const record = useRecordContext(props);
const oneSecondHasPassed = useTimeout(timeout);
const createPath = useCreatePath();

const { isPending, error, total } =
useReferenceManyFieldController<RecordType>({
filter,
sort,
page: 1,
perPage: 1,
record,
reference,
// @ts-ignore remove when #8491 is released
resource,
source,
target,
});

const body = isPending ? (
oneSecondHasPassed ? (
<CircularProgress size={14} />
) : (
''
)
) : error ? (
<ErrorIcon color="error" fontSize="small" titleAccess="error" />
) : (
total
const body = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just put it inside the main JSX directly? This body const is useless now IMO

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's used twice

<ReferenceManyCountBase
{...props}
loading={<CircularProgress size={14} />}
error={
<ErrorIcon color="error" fontSize="small" titleAccess="error" />
}
/>
);

return (
<StyledTypography
className={clsx(className, ReferenceManyCountClasses.root)}
Expand All @@ -101,7 +82,7 @@ export const ReferenceManyCount = <RecordType extends RaRecord = RaRecord>(
}),
search: `filter=${JSON.stringify({
...(filter || {}),
[target]: record[source],
[target]: get(record, source),
})}`,
}}
onClick={e => e.stopPropagation()}
Expand Down
3 changes: 1 addition & 2 deletions packages/ra-ui-materialui/src/field/ReferenceManyField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
useReferenceManyFieldController,
ListContextProvider,
ResourceContextProvider,
useRecordContext,
RaRecord,
UseReferenceManyFieldControllerParams,
} from 'ra-core';
Expand Down Expand Up @@ -69,6 +68,7 @@ export const ReferenceManyField = <
page = 1,
pagination = null,
perPage = 25,
record,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lint warning now

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

reference,
resource,
sort = defaultSort,
Expand All @@ -77,7 +77,6 @@ export const ReferenceManyField = <
target,
queryOptions,
} = props;
const record = useRecordContext(props);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no longer necessary since the controller hook does it.


const controllerProps = useReferenceManyFieldController<
RecordType,
Expand Down
Loading