Skip to content

Commit

Permalink
Merge pull request #3313 from marmelab/useReferenceInput
Browse files Browse the repository at this point in the history
[RFR] Use reference input
  • Loading branch information
fzaninotto authored Jul 22, 2019
2 parents d9251a1 + 70b1358 commit 7902574
Show file tree
Hide file tree
Showing 29 changed files with 1,795 additions and 849 deletions.
23 changes: 23 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,26 @@ export default (type, params) => {
// ...
}
```

## ReferenceInputController isLoading injected props renamed to loading

When using custom component with ReferenceInputController, you should rename the component `isLoading` prop to `loading`.

```diff
- <ReferenceInputController {...props}>
- {({ isLoading, otherProps }) => (
- <CustomReferenceInputView
- {...otherProps}
- isLoading={isLoading}
- />
- )}
- </ReferenceInputController>
+ <ReferenceInputController {...props}>
+ {({ loading, otherProps }) => (
+ <CustomReferenceInputView
+ {...otherProps}
+ loading={loading}
+ />
+ )}
+ </ReferenceInputController>
```
2 changes: 1 addition & 1 deletion docs/Inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ The child component may further filter results (that's the case, for instance, f
The child component receives the following props from `<ReferenceInput>`:
- `isLoading`: whether the request for possible values is loading or not
- `loading`: whether the request for possible values is loading or not
- `filter`: the current filter of the request for possible values. Defaults to `{}`.
- `pagination`: the current pagination of the request for possible values. Defaults to `{ page: 1, perPage: 25 }`.
- `sort`: the current sorting of the request for possible values. Defaults to `{ field: 'id', order: 'DESC' }`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('<ReferenceFieldController />', () => {
record={{ id: 1, postId: 123 }}
source="postId"
reference="posts"
resource="comments"
basePath=""
/>,
defaultState
Expand All @@ -35,6 +36,7 @@ describe('<ReferenceFieldController />', () => {
record={{ id: 1, postId: null }}
source="postId"
reference="posts"
resource="comments"
basePath=""
/>,
defaultState
Expand All @@ -51,15 +53,15 @@ describe('<ReferenceFieldController />', () => {
reference="posts"
resource="comments"
basePath="/comments"
crudGetManyAccumulate={crudGetManyAccumulate}
>
{children}
</ReferenceFieldController>,
defaultState
);

expect(children).toBeCalledWith({
isLoading: false,
loading: false,
loaded: true,
referenceRecord: { id: 123, title: 'foo' },
resourceLinkPath: '/posts/123',
});
Expand Down Expand Up @@ -89,7 +91,8 @@ describe('<ReferenceFieldController />', () => {
);

expect(children).toBeCalledWith({
isLoading: false,
loading: false,
loaded: true,
referenceRecord: { id: 123, title: 'foo' },
resourceLinkPath: '/prefix/posts/123',
});
Expand Down Expand Up @@ -119,7 +122,8 @@ describe('<ReferenceFieldController />', () => {
);

expect(children).toBeCalledWith({
isLoading: false,
loading: false,
loaded: true,
referenceRecord: { id: 123, title: 'foo' },
resourceLinkPath: '/edit/123',
});
Expand All @@ -134,7 +138,6 @@ describe('<ReferenceFieldController />', () => {
reference="show"
resource="edit"
basePath="/edit"
crudGetManyAccumulate={crudGetManyAccumulate}
>
{children}
</ReferenceFieldController>,
Expand All @@ -150,7 +153,8 @@ describe('<ReferenceFieldController />', () => {
);

expect(children).toBeCalledWith({
isLoading: false,
loading: false,
loaded: true,
referenceRecord: { id: 123, title: 'foo' },
resourceLinkPath: '/show/123',
});
Expand All @@ -173,7 +177,8 @@ describe('<ReferenceFieldController />', () => {
);

expect(children).toBeCalledWith({
isLoading: false,
loading: false,
loaded: true,
referenceRecord: { id: 123, title: 'foo' },
resourceLinkPath: '/posts/123/show',
});
Expand Down Expand Up @@ -204,7 +209,8 @@ describe('<ReferenceFieldController />', () => {
);

expect(children).toBeCalledWith({
isLoading: false,
loading: false,
loaded: true,
referenceRecord: { id: 123, title: 'foo' },
resourceLinkPath: '/edit/123/show',
});
Expand All @@ -219,7 +225,6 @@ describe('<ReferenceFieldController />', () => {
reference="show"
resource="edit"
basePath="/edit"
crudGetManyAccumulate={crudGetManyAccumulate}
link="show"
>
{children}
Expand All @@ -236,7 +241,8 @@ describe('<ReferenceFieldController />', () => {
);

expect(children).toBeCalledWith({
isLoading: false,
loading: false,
loaded: true,
referenceRecord: { id: 123, title: 'foo' },
resourceLinkPath: '/show/123/show',
});
Expand All @@ -249,6 +255,7 @@ describe('<ReferenceFieldController />', () => {
record={{ id: 1, postId: 123 }}
source="postId"
reference="posts"
resource="comments"
basePath="/foo"
link={false}
>
Expand All @@ -258,7 +265,8 @@ describe('<ReferenceFieldController />', () => {
);

expect(children).toBeCalledWith({
isLoading: false,
loading: false,
loaded: true,
referenceRecord: { id: 123, title: 'foo' },
resourceLinkPath: false,
});
Expand Down
25 changes: 18 additions & 7 deletions packages/ra-core/src/controller/field/ReferenceFieldController.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { FunctionComponent, ReactNode, ReactElement } from 'react';
import get from 'lodash/get';

import { Record } from '../../types';
import useReference, {
UseReferenceProps,
LinkToFunctionType,
} from './useReference';

import getResourceLinkPath, { LinkToFunctionType } from './getResourceLinkPath';
import useReference, { UseReferenceProps } from '../useReference';

interface childrenParams extends UseReferenceProps {
resourceLinkPath: string | false;
}

interface Props {
allowEmpty?: boolean;
basePath: string;
children: (params: UseReferenceProps) => ReactNode;
children: (params: childrenParams) => ReactNode;
record?: Record;
reference: string;
resource: string;
source: string;
link: string | boolean | LinkToFunctionType;
link?: string | boolean | LinkToFunctionType;
}

/**
Expand Down Expand Up @@ -47,9 +52,15 @@ interface Props {
*/
export const ReferenceFieldController: FunctionComponent<Props> = ({
children,
record,
source,
...props
}) => {
return children(useReference(props)) as ReactElement<any>;
const id = get(record, source);
return children({
...useReference({ ...props, id }),
resourceLinkPath: getResourceLinkPath({ ...props, record, source }),
}) as ReactElement<any>;
};

export default ReferenceFieldController;
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ interface Props {
total?: number;
}

const defaultPerPage = 25;

/**
* Render related records to the current one.
*
Expand Down Expand Up @@ -91,10 +93,10 @@ export const ReferenceManyFieldController: FunctionComponent<Props> = ({
sort: initialSort,
children,
}) => {
const { sort, setSort } = useSortState(initialSort);
const { page, perPage, setPage, setPerPage } = usePaginationState(
initialPerPage
);
const { sort, setSortField } = useSortState(initialSort);
const { page, perPage, setPage, setPerPage } = usePaginationState({
perPage: initialPerPage || defaultPerPage,
});
const {
data,
ids,
Expand Down Expand Up @@ -124,7 +126,7 @@ export const ReferenceManyFieldController: FunctionComponent<Props> = ({
referenceBasePath,
setPage,
setPerPage,
setSort,
setSort: setSortField,
total,
});
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { useEffect } from 'react';
// @ts-ignore
import { useDispatch, useSelector } from 'react-redux';
import get from 'lodash/get';

import { crudGetManyAccumulate } from '../../actions';
import { linkToRecord } from '../../util';
import { Record, ReduxState } from '../../types';
import { Record } from '../../types';

export type LinkToFunctionType = (record: Record, reference: string) => string;

Expand All @@ -15,75 +11,59 @@ interface Option {
allowEmpty?: boolean;
basePath: string;
record?: Record;
source: string;
reference: string;
resource: string;
source: string;
link: LinkToType;
link?: LinkToType;
linkType?: LinkToType; // deprecated, use link instead
}

export interface UseReferenceProps {
isLoading: boolean;
referenceRecord: Record;
resourceLinkPath: string | false;
}

/**
* @typedef ReferenceProps
* @type {Object}
* @property {boolean} isLoading: boolean indicating if the reference has loaded
* @property {boolean} loading: boolean indicating if the reference is loading
* @property {boolean} loaded: boolean indicating if the reference has loaded
* @property {Object} referenceRecord: the referenced record.
* @property {string | false} resourceLinkPath link to the page of the related record (depends on link) (false is no link)
*/

/**
* Fetch reference record, and return it when avaliable
*
* The reference prop sould be the name of one of the <Resource> components
* added as <Admin> child.
* Get the link toward the referenced resource
*
* @example
*
* const { isLoading, referenceRecord, resourceLinkPath } = useReference({
* source: 'userId',
* reference: 'users',
* record: {
* userId: 7
* }
* const linkPath = getResourceLinkPath({
* basePath: '/comments',
* link: 'edit',
* reference: 'users',
* record: {
* userId: 7
* },
* resource: 'comments',
* source: 'userId',
* });
*
* @param {Object} option
* @param {boolean} option.allowEmpty do we allow for no referenced record (default to false)
* @param {string} option.basePath basepath to current resource
* @param {string | false | LinkToFunctionType} option.link="edit" The link toward the referenced record. 'edit', 'show' or false for no link (default to edit). Alternatively a function that returns a string
* @param {string | false | LinkToFunctionType} [option.linkType] DEPRECATED : old name for link
* @param {Object} option.record The The current resource record
* @param {string} option.reference The linked resource name
* @param {Object} option.record The The current resource record
* @param {string} option.resource The current resource name
* @param {string} option.source The key of the linked resource identifier
*
* @returns {ReferenceProps} The reference props
*/
export const useReference = ({
allowEmpty = false,
const getResourceLinkPath = ({
basePath,
link = 'edit',
linkType,
record = { id: '' },
reference,
record = { id: '' },
resource,
source,
}: Option): UseReferenceProps => {
}: Option): string | false => {
const sourceId = get(record, source);
const referenceRecord = useSelector(
getReferenceRecord(sourceId, reference)
);
const dispatch = useDispatch();
useEffect(() => {
if (sourceId !== null && typeof sourceId !== 'undefined') {
dispatch(crudGetManyAccumulate(reference, [sourceId]));
}
}, [sourceId, reference]); // eslint-disable-line react-hooks/exhaustive-deps
const rootPath = basePath.replace(resource, reference);
// Backward compatibility: keep linkType but with warning
const getResourceLinkPath = (linkTo: LinkToType) =>
Expand All @@ -103,15 +83,7 @@ export const useReference = ({
linkType !== undefined ? linkType : link
);

return {
isLoading: !referenceRecord && !allowEmpty,
referenceRecord,
resourceLinkPath,
};
return resourceLinkPath;
};

const getReferenceRecord = (sourceId, reference) => (state: ReduxState) =>
state.admin.resources[reference] &&
state.admin.resources[reference].data[sourceId];

export default useReference;
export default getResourceLinkPath;
4 changes: 2 additions & 2 deletions packages/ra-core/src/controller/field/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import ReferenceArrayFieldController from './ReferenceArrayFieldController';
import ReferenceFieldController from './ReferenceFieldController';
import ReferenceManyFieldController from './ReferenceManyFieldController';
import useReference from './useReference';
import getResourceLinkPath from './getResourceLinkPath';
import useReferenceArray from './useReferenceArray';
import useReferenceMany from './useReferenceMany';

export {
useReferenceArray,
ReferenceArrayFieldController,
ReferenceFieldController,
useReference,
getResourceLinkPath,
useReferenceMany,
ReferenceManyFieldController,
};
Loading

0 comments on commit 7902574

Please sign in to comment.