Skip to content

Commit 044f6de

Browse files
committed
fix(renderer): use custom registerField function
1 parent 678dc37 commit 044f6de

File tree

13 files changed

+224
-34
lines changed

13 files changed

+224
-34
lines changed

__mocks__/with-provider.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
import { RendererContext } from '@data-driven-forms/react-form-renderer';
44
import Form from '@data-driven-forms/react-form-renderer/form';
55

6-
const RenderWithProvider = ({ value = { formOptions: {} }, children, onSubmit = () => {} }) => {
6+
const RenderWithProvider = ({ value = { formOptions: {internalRegisterField: jest.fn(), internalUnRegisterField: jest.fn()} }, children, onSubmit = () => {} }) => {
77
return (
88
<Form onSubmit={onSubmit}>
99
{() => (

packages/ant-component-mapper/src/tests/slider.test.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import React from 'react';
2-
import { Form as DDFForm } from '@data-driven-forms/react-form-renderer';
2+
import { Form as DDFForm, RendererContext } from '@data-driven-forms/react-form-renderer';
33
import { mount } from 'enzyme';
44
import { Slider as AntSlider, Form as OriginalForm } from 'antd';
55
import Slider from '../slider';
66
import FormGroup from '../form-group';
77

88
const Form = (props) => (
99
<OriginalForm>
10-
<DDFForm onSubmit={jest.fn()} {...props} />
10+
<RendererContext.Provider value={{ formOptions: { internalRegisterField: jest.fn(), internalUnRegisterField: jest.fn() } }}>
11+
<DDFForm onSubmit={jest.fn()} {...props} />
12+
</RendererContext.Provider>
1113
</OriginalForm>
1214
);
1315

packages/react-form-renderer/demo/index.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import React from 'react';
33
import PropTypes from 'prop-types';
44
import ReactDOM from 'react-dom';
5-
import { FormRenderer, useFieldApi, componentTypes } from '../src';
5+
import { FormRenderer, useFieldApi, componentTypes, useFormApi } from '../src';
66
import MuiTextField from '@material-ui/core/TextField';
77
import Grid from '@material-ui/core/Grid';
88

@@ -108,9 +108,18 @@ const TextField = (props) => {
108108
);
109109
};
110110

111-
const fields = [];
111+
const Spy = () => {
112+
const formApi = useFormApi();
113+
console.log(formApi);
114+
return null;
115+
};
116+
117+
const fields = [{
118+
name: 'optionsSpy',
119+
component: 'spy',
120+
}];
112121

113-
for (let index = 0; index < 1000; index++) {
122+
for (let index = 0; index < 10; index++) {
114123
fields.push({
115124
name: `field-${index}`,
116125
label: `Text field ${index}`,
@@ -134,7 +143,8 @@ const App = () => {
134143
<div style={{ padding: 20 }}>
135144
<FormRenderer
136145
componentMapper={{
137-
[componentTypes.TEXT_FIELD]: TextField
146+
[componentTypes.TEXT_FIELD]: TextField,
147+
spy: Spy
138148
}}
139149
onSubmit={console.log}
140150
FormTemplate={MuiFormTemplate}

packages/react-form-renderer/src/field-provider/field-provider.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ComponentType, ReactNode } from 'react';
33
export interface FieldProviderProps<T> {
44
Component?: ComponentType<any>;
55
render?: (props: T) => ReactNode;
6+
skipRegistration?: boolean;
67
}
78

89
declare const FieldProvider: React.ComponentType<FieldProviderProps<object>>;

packages/react-form-renderer/src/form-renderer/form-renderer.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,21 @@ const FormRenderer = ({
2626
...props
2727
}) => {
2828
const [fileInputs, setFileInputs] = useState([]);
29+
const registeredFields = useRef({});
2930
const focusDecorator = useRef(createFocusDecorator());
3031
let schemaError;
3132

33+
const setRegisteredFields = (fn => registeredFields.current = fn({...registeredFields.current}));
34+
const internalRegisterField = (name) => {
35+
setRegisteredFields(prev => prev[name] ? ({...prev, [name]: prev[name] + 1}) : ({...prev, [name]: 1}));
36+
};
37+
38+
const internalUnRegisterField = (name) => {
39+
setRegisteredFields(({[name]: currentField, ...prev}) => currentField && currentField > 1 ? ({[name]: currentField - 1, ...prev}) : prev);
40+
};
41+
42+
const internalGetRegisteredFields = () => Object.entries(registeredFields.current).reduce((acc, [name, value]) => value > 0 ? [...acc, name] : acc, []);
43+
3244
const validatorMapperMerged = { ...defaultValidatorMapper, ...validatorMapper };
3345

3446
try {
@@ -80,8 +92,12 @@ const FormRenderer = ({
8092
reset,
8193
clearOnUnmount,
8294
renderForm,
95+
internalRegisterField,
96+
internalUnRegisterField,
8397
...mutators,
84-
...form
98+
...form,
99+
ffGetRegisteredFields: form.getRegisteredFields,
100+
getRegisteredFields: internalGetRegisteredFields,
85101
}
86102
}}
87103
>

packages/react-form-renderer/src/form-renderer/render-form.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
33
import set from 'lodash/set';
44
import RendererContext from '../renderer-context';
55
import Condition from '../condition';
6-
import { Field } from 'react-final-form';
6+
import FieldProvider from '../field-provider';
77
import getConditionTriggers from '../get-condition-triggers';
88

99
const FormFieldHideWrapper = ({ hideField, children }) => (hideField ? <div hidden>{children}</div> : children);
@@ -42,18 +42,16 @@ const ConditionTriggerDetector = ({ values = {}, triggers = [], children, condit
4242

4343
const name = internalTriggers.shift();
4444
return (
45-
<Field name={name} subscription={{ value: true }}>
46-
{({input: {value}}) => (
47-
<ConditionTriggerDetector
48-
triggers={[...internalTriggers]}
49-
values={set({...values}, name, value)}
50-
condition={condition}
51-
field={field}
52-
>
53-
{children}
54-
</ConditionTriggerDetector>
55-
)}
56-
</Field>
45+
<FieldProvider skipRegistration name={name} subscription={{ value: true }} render={({input: {value}}) => (
46+
<ConditionTriggerDetector
47+
triggers={[...internalTriggers]}
48+
values={set({...values}, name, value)}
49+
condition={condition}
50+
field={field}
51+
>
52+
{children}
53+
</ConditionTriggerDetector>
54+
)}/>
5755
);
5856
};
5957

packages/react-form-renderer/src/renderer-context/renderer-context.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export interface FormOptions extends FormApi {
1414
handleSubmit: () => Promise<AnyObject | undefined> | undefined;
1515
clearedValue?: any;
1616
renderForm: (fields: Field[]) => ReactNode[];
17+
internalRegisterField: (name: string) => void;
18+
internalUnregisterField: (name: string) => void;
19+
getRegisteredFields: () => string[];
20+
ffGetRegisteredFields: () => string[];
1721
}
1822

1923
export interface RendererContextValue {

packages/react-form-renderer/src/tests/form-renderer/__snapshots__/render-form.test.js.snap

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,10 @@ exports[`renderForm function #condition should render condition field only if co
247247
]
248248
}
249249
>
250-
<ForwardRef(Field)
250+
<FieldProvider
251251
name="a"
252+
render={[Function]}
253+
skipRegistration={true}
252254
subscription={
253255
Object {
254256
"value": true,
@@ -289,8 +291,10 @@ exports[`renderForm function #condition should render condition field only if co
289291
}
290292
}
291293
>
292-
<ForwardRef(Field)
294+
<FieldProvider
293295
name="b"
296+
render={[Function]}
297+
skipRegistration={true}
294298
subscription={
295299
Object {
296300
"value": true,
@@ -331,8 +335,10 @@ exports[`renderForm function #condition should render condition field only if co
331335
}
332336
}
333337
>
334-
<ForwardRef(Field)
338+
<FieldProvider
335339
name="c"
340+
render={[Function]}
341+
skipRegistration={true}
336342
subscription={
337343
Object {
338344
"value": true,
@@ -432,11 +438,11 @@ exports[`renderForm function #condition should render condition field only if co
432438
/>
433439
</ConditionTriggerWrapper>
434440
</ConditionTriggerDetector>
435-
</ForwardRef(Field)>
441+
</FieldProvider>
436442
</ConditionTriggerDetector>
437-
</ForwardRef(Field)>
443+
</FieldProvider>
438444
</ConditionTriggerDetector>
439-
</ForwardRef(Field)>
445+
</FieldProvider>
440446
</ConditionTriggerDetector>
441447
</FormConditionWrapper>
442448
</SingleField>
@@ -672,8 +678,10 @@ exports[`renderForm function #condition should render condition field only if on
672678
]
673679
}
674680
>
675-
<ForwardRef(Field)
681+
<FieldProvider
676682
name="a"
683+
render={[Function]}
684+
skipRegistration={true}
677685
subscription={
678686
Object {
679687
"value": true,
@@ -707,8 +715,10 @@ exports[`renderForm function #condition should render condition field only if on
707715
}
708716
}
709717
>
710-
<ForwardRef(Field)
718+
<FieldProvider
711719
name="b"
720+
render={[Function]}
721+
skipRegistration={true}
712722
subscription={
713723
Object {
714724
"value": true,
@@ -787,9 +797,9 @@ exports[`renderForm function #condition should render condition field only if on
787797
/>
788798
</ConditionTriggerWrapper>
789799
</ConditionTriggerDetector>
790-
</ForwardRef(Field)>
800+
</FieldProvider>
791801
</ConditionTriggerDetector>
792-
</ForwardRef(Field)>
802+
</FieldProvider>
793803
</ConditionTriggerDetector>
794804
</FormConditionWrapper>
795805
</SingleField>

packages/react-form-renderer/src/tests/form-renderer/form-renderer.test.js

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
1-
import React from 'react';
1+
import React, { Fragment } from 'react';
2+
import { act } from 'react-dom/test-utils';
23
import { mount } from 'enzyme';
34
import toJson from 'enzyme-to-json';
45
import FormRenderer from '../../form-renderer';
56
import SchemaErrorComponent from '../../form-renderer/schema-error-component';
67
import componentTypes from '../../component-types';
78
import FormTemplate from '../../../../../__mocks__/mock-form-template';
89
import useFieldApi from '../../use-field-api';
10+
import useFormApi from '../../use-form-api';
11+
12+
const PropsSpy = () => <Fragment />;
13+
const ContextSpy = ({registerSpy, spyFF, ...props}) => {
14+
useFieldApi(props);
15+
const { getRegisteredFields, ffGetRegisteredFields, ...formApi } = useFormApi();
16+
return (
17+
<Fragment>
18+
<button onClick={() => registerSpy(spyFF ? ffGetRegisteredFields() : getRegisteredFields())} id={props.name}></button>
19+
<PropsSpy {...formApi} />
20+
</Fragment>
21+
);
22+
};
23+
24+
const DuplicatedField = ({name, ...props}) => {
25+
useFieldApi({name: name.split('@').pop(), ...props});
26+
return <Fragment />;
27+
};
928

1029
const TextField = (props) => {
1130
const { input } = useFieldApi(props);
@@ -181,4 +200,117 @@ describe('<FormRenderer />', () => {
181200
expect(onSubmit).toHaveBeenCalledWith({ 'initial-convert': [{ value: 5 }, { value: 3 }, { value: 11 }, { value: 999 }] });
182201
});
183202
});
203+
204+
it('should register new field to renderer context', () => {
205+
const registerSpy = jest.fn();
206+
const wrapper = mount(
207+
<FormRenderer
208+
FormTemplate={(props) => <FormTemplate {...props} />}
209+
componentMapper={{
210+
spy: {component: ContextSpy, registerSpy}
211+
}}
212+
schema={{ fields: [{component: 'spy', name: 'should-show'}] }}
213+
onSubmit={jest.fn()}
214+
/>
215+
);
216+
217+
const button = wrapper.find('button#should-show');
218+
act(() => {
219+
button.simulate('click');
220+
});
221+
expect(registerSpy).toHaveBeenCalledWith(['should-show']);
222+
});
223+
224+
it('should un-register field after unmount', () => {
225+
const registerSpy = jest.fn();
226+
const wrapper = mount(
227+
<FormRenderer
228+
FormTemplate={(props) => <FormTemplate {...props} />}
229+
componentMapper={{
230+
...componentMapper,
231+
spy: {component: ContextSpy, registerSpy}
232+
}}
233+
initialValues={{ x: 'a' }}
234+
schema={{ fields: [
235+
{component: 'spy', name: 'trigger'},
236+
{component: 'text-field', name: 'x'},
237+
{component: 'text-field', name: 'field-1', condition: {when: 'x', is: 'a'}}
238+
] }}
239+
onSubmit={jest.fn()}
240+
/>
241+
);
242+
243+
const button = wrapper.find('button#trigger');
244+
act(() => {
245+
button.simulate('click');
246+
});
247+
expect(registerSpy).toHaveBeenCalledWith(['trigger', 'x', 'field-1']);
248+
act(() => {
249+
wrapper.find('input').first().simulate('change', { target: { value: '' } });
250+
});
251+
act(() => {
252+
button.simulate('click');
253+
});
254+
expect(registerSpy).toHaveBeenCalledWith(['trigger', 'x']);
255+
});
256+
257+
it('should not un-register field after unmount with multiple fields coppies', () => {
258+
const registerSpy = jest.fn();
259+
const wrapper = mount(
260+
<FormRenderer
261+
FormTemplate={(props) => <FormTemplate {...props} />}
262+
componentMapper={{
263+
...componentMapper,
264+
spy: {component: ContextSpy, registerSpy},
265+
duplicate: DuplicatedField,
266+
}}
267+
initialValues={{ x: 'a' }}
268+
schema={{ fields: [
269+
{component: 'spy', name: 'trigger'},
270+
{component: 'text-field', name: 'x'},
271+
{component: 'text-field', name: 'field-1', condition: {when: 'x', is: 'a'}},
272+
{component: 'duplicate', name: 'dupe@field-1'}
273+
] }}
274+
onSubmit={jest.fn()}
275+
/>
276+
);
277+
278+
const button = wrapper.find('button#trigger');
279+
act(() => {
280+
button.simulate('click');
281+
});
282+
expect(registerSpy).toHaveBeenCalledWith(['trigger', 'x', 'field-1']);
283+
act(() => {
284+
wrapper.find('input').first().simulate('change', { target: { value: '' } });
285+
});
286+
act(() => {
287+
button.simulate('click');
288+
});
289+
expect(registerSpy).toHaveBeenCalledWith(['trigger', 'x', 'field-1']);
290+
});
291+
292+
it('should skip field registration', () => {
293+
const registerSpy = jest.fn();
294+
const wrapper = mount(
295+
<FormRenderer
296+
FormTemplate={(props) => <FormTemplate {...props} />}
297+
componentMapper={{
298+
...componentMapper,
299+
spy: {component: ContextSpy, registerSpy},
300+
duplicate: DuplicatedField,
301+
}}
302+
initialValues={{ x: 'a' }}
303+
schema={{ fields: [
304+
{component: 'spy', name: 'trigger', skipRegistration: true},
305+
] }}
306+
onSubmit={jest.fn()}
307+
/>
308+
);
309+
310+
const button = wrapper.find('button#trigger');
311+
act(() => {
312+
button.simulate('click');
313+
});
314+
expect(registerSpy).toHaveBeenCalledWith([]);
315+
});
184316
});

0 commit comments

Comments
 (0)