Skip to content

Commit 8bd8568

Browse files
committed
fix(renderer): serialize validate config to prevent infinite loop
1 parent 263c042 commit 8bd8568

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
/* eslint-disable camelcase */
22
import React from 'react';
33
import ReactDOM from 'react-dom';
4-
import FormRenderer from '../src';
4+
import FormRenderer, { useFieldApi, validatorTypes, componentTypes } from '../src';
55
import componentMapper from './form-fields-mapper';
66
import FormTemplate from './form-template';
77

8+
// eslint-disable-next-line react/prop-types
9+
const TextField = ({ name, label }) => {
10+
const { input } = useFieldApi({ name, validate: [{ type: validatorTypes.REQUIRED }] });
11+
return (
12+
<div>
13+
<label>{label}</label>
14+
<input {...input} />
15+
</div>
16+
);
17+
};
18+
819
const fileSchema = {
920
fields: [
1021
{
1122
component: 'text-field',
12-
name: 'file-upload',
13-
type: 'file',
14-
label: 'file upload'
23+
name: 'foo',
24+
label: 'Foo'
1525
}
1626
]
1727
};
@@ -21,7 +31,10 @@ const App = () => {
2131
return (
2232
<div style={{ padding: 20 }}>
2333
<FormRenderer
24-
componentMapper={componentMapper}
34+
componentMapper={{
35+
...componentMapper,
36+
[componentTypes.TEXT_FIELD]: TextField
37+
}}
2538
onSubmit={(values, ...args) => console.log(values, args)}
2639
FormTemplate={FormTemplate}
2740
schema={fileSchema}

packages/react-form-renderer/src/files/use-field-api.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,13 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, res
9696
arrayValidator: calculateArrayValidator(props, validate, component, validatorMapper)
9797
});
9898
}
99-
}, [validate, component, props.dataType]);
99+
/**
100+
* We have to stringify the validate array in order to preven infinite looping when validate was passed directly to useFieldApi
101+
* const x = useFieldApu({name: 'foo', validate: [{type: 'bar'}]}) will trigger infinite looping witouth the serialize.
102+
* Using stringify is acceptable here since the array is usually very small.
103+
* If we notice performance hit, we can implement custom hook with a deep equal functionality.
104+
*/
105+
}, [validate ? JSON.stringify(validate) : false, component, props.dataType]);
100106

101107
/** Re-convert initialValue when changed */
102108
useEffect(() => {

packages/react-form-renderer/src/tests/form-renderer/use-field-api.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import useFieldApi from '../../files/use-field-api';
66
import componentTypes from '../../files/component-types';
77
import Form from '../../files/form';
88
import RendererContext from '../../files/renderer-context';
9+
import validatorTypes from '../../files/validator-types';
910

1011
describe('useFieldApi', () => {
1112
const Catcher = ({ children }) => children;
@@ -179,4 +180,39 @@ describe('useFieldApi', () => {
179180
expect(wrapper.find('input').prop('value')).toEqual('');
180181
expect(registerInputFileSpy).toHaveBeenCalledWith('file-input');
181182
});
183+
184+
it('should not crash when passing validate directly', () => {
185+
const TestDummy = ({ validate }) => {
186+
const { input } = useFieldApi({
187+
name: 'foo',
188+
validate: validate ? [{ type: validatorTypes.REQUIRED }] : [{ type: validatorTypes.URL }]
189+
});
190+
return <input {...input} id="foo" />;
191+
};
192+
193+
const FormWrapper = ({ validate = true }) => (
194+
<Form onSubmit={jest.fn()}>
195+
{({ handleSubmit }) => (
196+
<form onSubmit={handleSubmit}>
197+
<RendererContext.Provider
198+
value={{
199+
validatorMapper: { required: () => (value) => (!value ? 'required' : undefined), url: () => jest.fn() },
200+
formOptions: {}
201+
}}
202+
>
203+
<TestDummy validate={validate} />
204+
</RendererContext.Provider>
205+
</form>
206+
)}
207+
</Form>
208+
);
209+
210+
const wrapper = mount(<FormWrapper />);
211+
expect(wrapper.find('input')).toHaveLength(1);
212+
wrapper.find('#foo').simulate('change', { target: { value: 'bar' } });
213+
wrapper.update();
214+
wrapper.setProps({ validate: false });
215+
wrapper.update();
216+
expect(wrapper.find('input')).toHaveLength(1);
217+
});
182218
});

0 commit comments

Comments
 (0)