Skip to content

Commit 325b1dd

Browse files
authored
fix(react): rendering FormTemplate when undefined
- cleanup to follow best practices for readability, pass props, and extensibility - implemented memoization for callbacks and merged data to improve re-renders - removed logic to choose between FormTemplate and children instead satisfies both independently of one another - removed prop requirement for onSubmit for edge cases, really not necessary if checked before calling
1 parent d603ef3 commit 325b1dd

File tree

1 file changed

+117
-58
lines changed

1 file changed

+117
-58
lines changed

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

Lines changed: 117 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,132 @@
1-
import React, { useState, useRef } from 'react';
2-
import Form from '../form';
31
import arrayMutators from 'final-form-arrays';
4-
import PropTypes from 'prop-types';
52
import createFocusDecorator from 'final-form-focus';
3+
import PropTypes from 'prop-types';
4+
import React, {useCallback, useMemo, useRef, useState} from 'react';
65

6+
import defaultSchemaValidator from '../default-schema-validator';
7+
import defaultValidatorMapper from '../validator-mapper';
8+
import Form from '../form';
79
import RendererContext from '../renderer-context';
810
import renderForm from './render-form';
9-
import defaultSchemaValidator from '../default-schema-validator';
1011
import SchemaErrorComponent from './schema-error-component';
11-
import defaultValidatorMapper from '../validator-mapper';
12+
13+
14+
const isFunc = (fn) => (typeof fn === 'function');
1215

1316
const FormRenderer = ({
17+
actionMapper,
1418
children,
19+
clearedValue,
20+
clearOnUnmount,
1521
componentMapper,
22+
decorators,
1623
FormTemplate,
1724
FormTemplateProps,
18-
onSubmit,
25+
mutators,
1926
onCancel,
27+
onError,
2028
onReset,
21-
clearOnUnmount,
22-
subscription,
23-
clearedValue,
29+
onSubmit,
2430
schema,
25-
validatorMapper,
26-
actionMapper,
2731
schemaValidatorMapper,
32+
subscription,
33+
validatorMapper,
2834
...props
2935
}) => {
3036
const [fileInputs, setFileInputs] = useState([]);
3137
const registeredFields = useRef({});
3238
const focusDecorator = useRef(createFocusDecorator());
33-
let schemaError;
39+
const validatorMapperMerged = useMemo(() => ({
40+
...defaultValidatorMapper,
41+
...validatorMapper
42+
}), [validatorMapper]);
43+
const mutatorsMerged = useMemo(() => ({
44+
...arrayMutators,
45+
...mutators
46+
}), [mutators]);
47+
const decoratorsMerged = useMemo(() => ([
48+
focusDecorator.current,
49+
...(Array.isArray(decorators) ? decorators : [])
50+
]), [decorators]);
51+
52+
const handleSubmitCallback = useCallback((values, formApi, ...args) => {
53+
return !isFunc(onSubmit) ? void 0 : onSubmit(values, {...formApi, fileInputs}, ...args);
54+
}, [onSubmit, fileInputs]);
55+
56+
const handleCancelCallback = useCallback((getState) => (...args) => {
57+
return !isFunc(onCancel) ? void 0 : onCancel(getState().values, ...args);
58+
}, [onCancel]);
59+
60+
const handleResetCallback = useCallback((reset) => (...args) => {
61+
reset();
62+
return !isFunc(onReset) ? void 0 : onReset(...args);
63+
}, [onReset]);
3464

35-
const setRegisteredFields = (fn) => (registeredFields.current = fn({ ...registeredFields.current }));
36-
const internalRegisterField = (name) => {
37-
setRegisteredFields((prev) => (prev[name] ? { ...prev, [name]: prev[name] + 1 } : { ...prev, [name]: 1 }));
38-
};
65+
const handleErrorCallback = useCallback((...args) => {
66+
// eslint-disable-next-line no-console
67+
console.error(...args);
68+
return !isFunc(onError) ? void 0 : onError(...args);
69+
}, [onError]);
70+
71+
const registerInputFile = useCallback((name) => {
72+
setFileInputs((prevFiles) => [...prevFiles, name]);
73+
}, []);
74+
75+
const unRegisterInputFile = useCallback((name) => {
76+
setFileInputs((prevFiles) => [
77+
...prevFiles.splice(prevFiles.indexOf(name))
78+
]);
79+
}, []);
3980

40-
const internalUnRegisterField = (name) => {
41-
setRegisteredFields(({ [name]: currentField, ...prev }) => (currentField && currentField > 1 ? { [name]: currentField - 1, ...prev } : prev));
42-
};
81+
const setRegisteredFields = useCallback((fn) => {
82+
return registeredFields.current = fn({...registeredFields.current});
83+
}, []);
4384

44-
const internalGetRegisteredFields = () =>
45-
Object.entries(registeredFields.current).reduce((acc, [name, value]) => (value > 0 ? [...acc, name] : acc), []);
85+
const internalRegisterField = useCallback((name) => {
86+
setRegisteredFields((prev) => (
87+
prev[name] ? {...prev, [name]: prev[name] + 1} : {...prev, [name]: 1})
88+
);
89+
}, [setRegisteredFields]);
4690

47-
const validatorMapperMerged = { ...defaultValidatorMapper, ...validatorMapper };
91+
const internalUnRegisterField = useCallback((name) => {
92+
setRegisteredFields(({[name]: currentField, ...prev}) => (
93+
currentField && currentField > 1 ? {[name]: currentField - 1, ...prev} : prev
94+
));
95+
}, [setRegisteredFields]);
96+
97+
const internalGetRegisteredFields = useCallback(() => {
98+
const fields = registeredFields.current;
99+
Object.entries(fields).reduce((acc, [name, value]) => (
100+
value > 0 ? [...acc, name] : acc
101+
), []);
102+
}, []);
48103

49104
try {
50105
const validatorTypes = Object.keys(validatorMapperMerged);
51106
const actionTypes = actionMapper ? Object.keys(actionMapper) : [];
52-
defaultSchemaValidator(schema, componentMapper, validatorTypes, actionTypes, schemaValidatorMapper);
53-
} catch (error) {
54-
schemaError = error;
55-
// eslint-disable-next-line no-console
56-
console.error(error);
57-
// eslint-disable-next-line no-console
58-
console.log('error: ', error.message);
59-
}
60107

61-
if (schemaError) {
62-
return <SchemaErrorComponent name={schemaError.name} message={schemaError.message} />;
108+
defaultSchemaValidator(
109+
schema,
110+
componentMapper,
111+
validatorTypes,
112+
actionTypes,
113+
schemaValidatorMapper
114+
);
115+
}
116+
catch (error) {
117+
handleErrorCallback('schema-error', error);
118+
return <SchemaErrorComponent name={error.name} message={error.message} />;
63119
}
64120

65-
const registerInputFile = (name) => setFileInputs((prevFiles) => [...prevFiles, name]);
66-
67-
const unRegisterInputFile = (name) => setFileInputs((prevFiles) => [...prevFiles.splice(prevFiles.indexOf(name))]);
121+
const formFields = useMemo(() => renderForm(schema.fields), [schema]);
68122

69123
return (
70124
<Form
71-
{...props}
72-
onSubmit={(values, formApi, ...args) => onSubmit(values, { ...formApi, fileInputs }, ...args)}
73-
mutators={{ ...arrayMutators }}
74-
decorators={[focusDecorator.current]}
75-
subscription={{ pristine: true, submitting: true, valid: true, ...subscription }}
76-
render={({ handleSubmit, pristine, valid, form: { reset, mutators, getState, submit, ...form } }) => (
125+
onSubmit={handleSubmitCallback}
126+
mutators={mutatorsMerged}
127+
decorators={decoratorsMerged}
128+
subscription={{pristine: true, submitting: true, valid: true, ...subscription}}
129+
render={({handleSubmit, pristine, valid, form: {reset, mutators, getState, submit, ...form}}) => (
77130
<RendererContext.Provider
78131
value={{
79132
componentMapper,
@@ -84,11 +137,9 @@ const FormRenderer = ({
84137
unRegisterInputFile,
85138
pristine,
86139
onSubmit,
87-
onCancel: onCancel ? (...args) => onCancel(getState().values, ...args) : undefined,
88-
onReset: (...args) => {
89-
onReset && onReset(...args);
90-
reset();
91-
},
140+
onCancel: handleCancelCallback(getState),
141+
onReset: handleResetCallback(reset),
142+
onError: handleErrorCallback,
92143
getState,
93144
valid,
94145
clearedValue,
@@ -107,33 +158,41 @@ const FormRenderer = ({
107158
},
108159
}}
109160
>
110-
{typeof children === 'function' ? children({schema, formFields: renderForm(schema.fields)}) : (
111-
<React.Fragment>
112-
<FormTemplate
113-
formFields={renderForm(schema.fields)}
114-
schema={schema}
115-
{...FormTemplateProps}
116-
/>
117-
{children}
118-
</React.Fragment>
161+
162+
{FormTemplate && (
163+
<FormTemplate
164+
formFields={formFields}
165+
schema={schema}
166+
{...FormTemplateProps}
167+
/>
119168
)}
169+
170+
{isFunc(children) ? children({formFields, schema}) : children}
171+
120172
</RendererContext.Provider>
121173
)}
174+
{...props}
122175
/>
123176
);
124177
};
125178

126179
FormRenderer.propTypes = {
127180
children: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
128-
onSubmit: PropTypes.func.isRequired,
181+
onSubmit: PropTypes.func,
129182
onCancel: PropTypes.func,
130183
onReset: PropTypes.func,
184+
onError: PropTypes.func,
131185
schema: PropTypes.object.isRequired,
132186
clearOnUnmount: PropTypes.bool,
133-
subscription: PropTypes.shape({ [PropTypes.string]: PropTypes.bool }),
187+
subscription: PropTypes.shape({[PropTypes.string]: PropTypes.bool}),
134188
clearedValue: PropTypes.any,
135189
componentMapper: PropTypes.shape({
136-
[PropTypes.string]: PropTypes.oneOfType([PropTypes.node, PropTypes.element, PropTypes.func, PropTypes.elementType]),
190+
[PropTypes.string]: PropTypes.oneOfType([
191+
PropTypes.node,
192+
PropTypes.element,
193+
PropTypes.func,
194+
PropTypes.elementType
195+
]),
137196
}).isRequired,
138197
FormTemplate: PropTypes.elementType,
139198
FormTemplateProps: PropTypes.object,

0 commit comments

Comments
 (0)