Skip to content

Commit

Permalink
Merge pull request #3294 from marmelab/useReferenceArrayField
Browse files Browse the repository at this point in the history
[RFR] add UseReferenceArrayField hook
  • Loading branch information
fzaninotto authored Jun 4, 2019
2 parents a5ed135 + 1218892 commit 426fae1
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -1,124 +1,183 @@
import React from 'react';
import assert from 'assert';
import { shallow } from 'enzyme';
import { UnconnectedReferenceArrayFieldController as ReferenceArrayFieldController } from './ReferenceArrayFieldController';
import { cleanup } from 'react-testing-library';

describe('<ReferenceArrayFieldController />', () => {
const crudGetManyAccumulate = jest.fn();
import ReferenceArrayFieldController from './ReferenceArrayFieldController';
import renderWithRedux from '../../util/renderWithRedux';
import { crudGetManyAccumulate } from '../../actions';

describe('<ReferenceArrayFieldController />', () => {
afterEach(cleanup);
it('should set the loadedOnce prop to false when related records are not yet fetched', () => {
const children = jest.fn();
const children = jest.fn().mockReturnValue('child');

shallow(
renderWithRedux(
<ReferenceArrayFieldController
record={{ id: 1, barIds: [1, 2] }}
resource="foo"
reference="bar"
source="barIds"
basePath=""
data={null}
ids={[1, 2]}
crudGetManyAccumulate={crudGetManyAccumulate}
record={{ id: 1, barIds: [1, 2] }}
source="barIds"
>
{children}
</ReferenceArrayFieldController>
</ReferenceArrayFieldController>,
{
admin: {
resources: {
bar: {
data: {},
},
},
},
}
);
assert.equal(children.mock.calls[0][0].loadedOnce, false);
expect(children.mock.calls[0][0]).toEqual({
currentSort: { field: 'id', order: 'ASC' },
loadedOnce: false,
referenceBasePath: '',
data: null,
ids: [1, 2],
});
});

it('should set the loadedOnce prop to true when at least one related record is found', () => {
const children = jest.fn();
const children = jest.fn().mockReturnValue('child');

shallow(
renderWithRedux(
<ReferenceArrayFieldController
record={{ id: 1, barIds: [1, 2] }}
resource="foo"
reference="bar"
source="barIds"
basePath=""
data={{ 1: { id: 1 } }}
ids={[1, 2]}
crudGetManyAccumulate={crudGetManyAccumulate}
>
{children}
</ReferenceArrayFieldController>
</ReferenceArrayFieldController>,
{
admin: {
resources: {
bar: {
data: {
2: {
id: 2,
title: 'hello',
},
},
},
},
},
}
);

assert.equal(children.mock.calls[0][0].loadedOnce, true);
expect(children.mock.calls[0][0]).toEqual({
currentSort: { field: 'id', order: 'ASC' },
loadedOnce: true,
referenceBasePath: '',
data: {
2: {
id: 2,
title: 'hello',
},
},
ids: [1, 2],
});
});

it('should set the data prop to the loaded data when it has been fetched', () => {
const children = jest.fn();
const data = {
1: { id: 1, title: 'hello' },
2: { id: 2, title: 'world' },
};
shallow(
const children = jest.fn().mockReturnValue('child');
renderWithRedux(
<ReferenceArrayFieldController
record={{ id: 1, barIds: [1, 2] }}
resource="foo"
reference="bar"
source="barIds"
basePath=""
data={data}
ids={[1, 2]}
crudGetManyAccumulate={crudGetManyAccumulate}
>
{children}
</ReferenceArrayFieldController>
</ReferenceArrayFieldController>,
{
admin: {
resources: {
bar: {
data: {
1: { id: 1, title: 'hello' },
2: { id: 2, title: 'world' },
},
},
},
},
}
);
assert.equal(children.mock.calls[0][0].loadedOnce, true);
assert.deepEqual(children.mock.calls[0][0].data, data);
assert.deepEqual(children.mock.calls[0][0].ids, [1, 2]);
expect(children.mock.calls[0][0]).toEqual({
currentSort: { field: 'id', order: 'ASC' },
loadedOnce: true,
referenceBasePath: '',
data: {
1: { id: 1, title: 'hello' },
2: { id: 2, title: 'world' },
},
ids: [1, 2],
});
});

it('should support record with string identifier', () => {
const children = jest.fn();
const data = {
'abc-1': { id: 'abc-1', title: 'hello' },
'abc-2': { id: 'abc-2', title: 'world' },
};
shallow(
const children = jest.fn().mockReturnValue('child');
renderWithRedux(
<ReferenceArrayFieldController
record={{ id: 1, barIds: ['abc-1', 'abc-2'] }}
resource="foo"
reference="bar"
source="barIds"
basePath=""
data={data}
ids={['abc-1', 'abc-2']}
crudGetManyAccumulate={crudGetManyAccumulate}
>
{children}
</ReferenceArrayFieldController>
</ReferenceArrayFieldController>,
{
admin: {
resources: {
bar: {
data: {
'abc-1': { id: 'abc-1', title: 'hello' },
'abc-2': { id: 'abc-2', title: 'world' },
},
},
},
},
}
);
assert.equal(children.mock.calls[0][0].loadedOnce, true);
assert.deepEqual(children.mock.calls[0][0].data, data);
assert.deepEqual(children.mock.calls[0][0].ids, ['abc-1', 'abc-2']);
expect(children.mock.calls[0][0]).toEqual({
currentSort: { field: 'id', order: 'ASC' },
loadedOnce: true,
referenceBasePath: '',
data: {
'abc-1': { id: 'abc-1', title: 'hello' },
'abc-2': { id: 'abc-2', title: 'world' },
},
ids: ['abc-1', 'abc-2'],
});
});

it('should support record with number identifier', () => {
const children = jest.fn();
const data = {
1: { id: 1, title: 'hello' },
2: { id: 2, title: 'world' },
};
shallow(
it('should dispatch crudGetManyAccumulate', () => {
const children = jest.fn().mockReturnValue('child');
const { dispatch } = renderWithRedux(
<ReferenceArrayFieldController
record={{ id: 1, barIds: [1, 2] }}
resource="foo"
reference="bar"
source="barIds"
basePath=""
data={data}
ids={[1, 2]}
crudGetManyAccumulate={crudGetManyAccumulate}
>
{children}
</ReferenceArrayFieldController>
</ReferenceArrayFieldController>,
{
admin: {
resources: {
bar: {
data: {},
},
},
},
}
);
assert.equal(children.mock.calls[0][0].loadedOnce, true);
assert.deepEqual(children.mock.calls[0][0].data, data);
assert.deepEqual(children.mock.calls[0][0].ids, [1, 2]);
expect(dispatch).toBeCalledWith(crudGetManyAccumulate('bar', [1, 2]));
});
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import { Component, ReactNode } from 'react';
import { connect } from 'react-redux';
import get from 'lodash/get';
import { FunctionComponent, ReactNode, ReactElement } from 'react';

import { crudGetManyAccumulate as crudGetManyAccumulateAction } from '../../actions';
import { getReferencesByIds } from '../../reducer/admin/references/oneToMany';
import {
ReduxState,
Record,
RecordMap,
Dispatch,
Sort,
Identifier,
} from '../../types';
import useReferenceArray from './useReferenceArray';
import { Identifier, RecordMap, Record, Sort } from '../..';

interface ChildrenFuncParams {
loadedOnce: boolean;
Expand All @@ -24,9 +14,6 @@ interface ChildrenFuncParams {
interface Props {
basePath: string;
children: (params: ChildrenFuncParams) => ReactNode;
crudGetManyAccumulate: Dispatch<typeof crudGetManyAccumulateAction>;
data?: RecordMap;
ids: Identifier[];
record?: Record;
reference: string;
resource: string;
Expand Down Expand Up @@ -65,64 +52,27 @@ interface Props {
* </ReferenceArrayField>
*
*/
export class UnconnectedReferenceArrayFieldController extends Component<Props> {
componentDidMount() {
this.fetchReferences();
}

componentWillReceiveProps(nextProps) {
if (
(this.props.record || { id: undefined }).id !==
(nextProps.record || {}).id
) {
this.fetchReferences(nextProps);
}
}

fetchReferences({ crudGetManyAccumulate, reference, ids } = this.props) {
crudGetManyAccumulate(reference, ids);
}

render() {
const {
const ReferenceArrayFieldController: FunctionComponent<Props> = ({
resource,
reference,
basePath,
record,
source,
children,
}) => {
return children({
currentSort: {
field: 'id',
order: 'ASC',
},
...useReferenceArray({
resource,
reference,
data,
ids,
children,
basePath,
} = this.props;

const referenceBasePath = basePath.replace(resource, reference); // FIXME obviously very weak

return children({
// tslint:disable-next-line:triple-equals
loadedOnce: data != undefined,
ids,
data,
referenceBasePath,
currentSort: {
field: 'id',
order: 'ASC',
},
});
}
}

const mapStateToProps = (state: ReduxState, props: Props) => {
const { record, source, reference } = props;
const ids = get(record, source) || [];
return {
data: getReferencesByIds(state, reference, ids),
ids,
};
record,
source,
}),
}) as ReactElement<any>;
};

const ReferenceArrayFieldController = connect(
mapStateToProps,
{
crudGetManyAccumulate: crudGetManyAccumulateAction,
}
)(UnconnectedReferenceArrayFieldController);

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

export {
useReferenceArray,
ReferenceArrayFieldController,
ReferenceFieldController,
useReference,
Expand Down
Loading

0 comments on commit 426fae1

Please sign in to comment.