Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] Use reference input #3313

Merged
merged 48 commits into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
02c488b
refactor ReferenceInputController into a function component
ThieryMichel Jun 6, 2019
4b3f748
replace hoc by hook
ThieryMichel Jun 10, 2019
a9acc8a
extract useFilterState in its own file
ThieryMichel Jun 13, 2019
1caedca
rename useReference to useReferenceField and add more generic useRefe…
ThieryMichel Jun 13, 2019
7c59c95
add useReferenceSearch hook
ThieryMichel Jun 13, 2019
8d00d20
sipmler useReferenceSearch hook
ThieryMichel Jun 17, 2019
d60bc81
rename useReferenceSearch to useMatchingReferences
ThieryMichel Jun 19, 2019
cab8d99
initialize selector on mount for useMatchingReferences and useReferen…
ThieryMichel Jun 19, 2019
d63e242
WIP
ThieryMichel Jun 19, 2019
72deb25
fix TestContext to not mutate its default store
ThieryMichel Jun 19, 2019
9ae131b
add test on useReference and useMatchingReferences
ThieryMichel Jun 19, 2019
3e9e879
add test on usePaginationState hook
ThieryMichel Jun 19, 2019
96ad01f
move hooks helper in util and use UsePrevious and useDeepCompareEffec…
ThieryMichel Jun 19, 2019
a55ca2f
add test on useSortState
ThieryMichel Jun 19, 2019
f152f8a
add test on useFilterState
ThieryMichel Jun 25, 2019
970ebe8
update ReferenceInputCOntroller test
ThieryMichel Jun 29, 2019
9513bd8
fix test
ThieryMichel Jul 4, 2019
64e2d24
prettier
ThieryMichel Jul 4, 2019
bfc329c
Update packages/ra-core/src/controller/useFilterState.spec.ts
ThieryMichel Jul 9, 2019
aaeb45d
Update packages/ra-core/src/controller/input/useMatchingReferences.ts
ThieryMichel Jul 9, 2019
f933e9d
Update packages/ra-core/src/controller/input/useMatchingReferences.ts
ThieryMichel Jul 9, 2019
bf5dc6c
Update packages/ra-core/src/controller/useReference.spec.ts
ThieryMichel Jul 9, 2019
0bb73be
Update packages/ra-core/src/controller/useReference.spec.ts
ThieryMichel Jul 9, 2019
cd6b801
Update packages/ra-core/src/controller/input/useMatchingReferences.sp…
ThieryMichel Jul 9, 2019
65b38c6
Update packages/ra-core/src/controller/input/useMatchingReferences.sp…
ThieryMichel Jul 9, 2019
a6209f3
Update packages/ra-core/src/controller/input/useMatchingReferences.sp…
ThieryMichel Jul 9, 2019
2d76d39
Update packages/ra-core/src/controller/input/useMatchingReferences.sp…
ThieryMichel Jul 9, 2019
0d3f70f
Update packages/ra-core/src/controller/input/useMatchingReferences.sp…
ThieryMichel Jul 9, 2019
6586b7f
Update packages/ra-core/src/controller/input/useMatchingReferences.sp…
ThieryMichel Jul 9, 2019
3ce3cdf
Update packages/ra-core/src/controller/input/useMatchingReferences.sp…
ThieryMichel Jul 9, 2019
44e8f9d
Update packages/ra-core/src/controller/input/useMatchingReferences.sp…
ThieryMichel Jul 9, 2019
c01c08d
Update packages/ra-core/src/controller/input/ReferenceInputController…
ThieryMichel Jul 9, 2019
352aadc
Update packages/ra-core/src/controller/usePaginationState.spec.ts
ThieryMichel Jul 9, 2019
753a7ff
code review
ThieryMichel Jul 9, 2019
ffd69d5
merge renderHookWIthRedux with renderHook
ThieryMichel Jul 10, 2019
d4653df
add jsdoc on renderHook
ThieryMichel Jul 10, 2019
11b0683
update useReference and useReferenceField hook doc
ThieryMichel Jul 10, 2019
79b11f6
add useReferenceInput hook
ThieryMichel Jul 10, 2019
7a1fd18
code review
ThieryMichel Jul 10, 2019
7f33c2f
have sort reducer use action
ThieryMichel Jul 10, 2019
1d69a5c
add setSort usage in jsdoc
ThieryMichel Jul 10, 2019
0a3535d
replace useReferenceField by getResourceLinkPath without useReference
ThieryMichel Jul 10, 2019
03da9bb
code review
ThieryMichel Jul 10, 2019
d71b5ab
refactor useGetMatchingReference to stop using createSelector
ThieryMichel Jul 10, 2019
0f51d8a
fix ReferenceManyField
ThieryMichel Jul 10, 2019
4bcb951
fix ReferenceFieldCOntroller
ThieryMichel Jul 10, 2019
173cb12
code review
ThieryMichel Jul 18, 2019
70b1358
Typo fix s/props/prop/
sedy-bot Jul 22, 2019
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
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
Copy link
Member

Choose a reason for hiding this comment

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

this is a BC break ; please add a migration guide in the UPDATE.md file

Copy link
Collaborator

Choose a reason for hiding this comment

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

And btw, why renaming it ?

- `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