Skip to content

Commit c4e29fd

Browse files
authored
Merge pull request #1238 from data-driven-forms/update-children-rendering
Update children rendering
2 parents 633f024 + 2fa6a34 commit c4e29fd

File tree

5 files changed

+91
-2
lines changed

5 files changed

+91
-2
lines changed

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,33 @@ import SchemaErrorComponent from './schema-error-component';
1212

1313
const isFunc = (fn) => typeof fn === 'function';
1414

15+
const renderChildren = (children, props) => {
16+
if (isFunc(children)) {
17+
return children(props);
18+
}
19+
20+
let childElement = children;
21+
if (Array.isArray(children)) {
22+
/**
23+
* Only permit one child element
24+
*/
25+
if (children.length !== 1) {
26+
throw new Error('FormRenderer expects only one child element!');
27+
}
28+
29+
childElement = children[0];
30+
}
31+
32+
if (typeof childElement === 'object') {
33+
/**
34+
* Clone react element, pass form fields and schema as props, but override them with child props if present
35+
*/
36+
return cloneElement(children, { ...props, ...childElement.props });
37+
}
38+
39+
throw new Error(`Invalid children prop! Expected one of [null, Function, object], got ${typeof children}`);
40+
};
41+
1542
const FormRenderer = ({
1643
actionMapper,
1744
children,
@@ -144,13 +171,13 @@ const FormRenderer = ({
144171
ffGetRegisteredFields: form.getRegisteredFields,
145172
getRegisteredFields: internalGetRegisteredFields,
146173
initialValues: props.initialValues,
174+
schema,
147175
},
148176
}}
149177
>
150178
{FormTemplate && <FormTemplate formFields={formFields} schema={schema} {...FormTemplateProps} />}
151179

152-
{isFunc(children) && children({ formFields, schema })}
153-
{typeof children === 'object' && cloneElement(children, { formFields, schema })}
180+
{children && renderChildren(children, { formFields, schema })}
154181
</RendererContext.Provider>
155182
)}
156183
{...props}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ValidatorMapper } from '../validator-mapper';
55
import { ActionMapper } from '../form-renderer';
66
import Field from '../common-types/field';
77
import { AnyObject } from '../common-types/any-object';
8+
import Schema from '../common-types/schema';
89

910
export interface FormOptions extends FormApi {
1011
registerInputFile?: (name: string) => void;
@@ -19,6 +20,7 @@ export interface FormOptions extends FormApi {
1920
getRegisteredFields: () => string[];
2021
ffGetRegisteredFields: () => string[];
2122
initialValues: AnyObject;
23+
schema: Schema,
2224
}
2325

2426
export interface RendererContextValue {

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,5 +384,42 @@ describe('<FormRenderer />', () => {
384384

385385
expect(submitSpy).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Object), expect.any(Function));
386386
});
387+
388+
it('should render null as a child', () => {
389+
expect(() => {
390+
render(
391+
<FormRenderer initialValues={{ foo: 'bar' }} componentMapper={componentMapper} schema={schema}>
392+
{null}
393+
</FormRenderer>
394+
);
395+
}).not.toThrow();
396+
});
397+
398+
it('should throw an error if more than one child was passed', () => {
399+
expect(() => {
400+
render(
401+
<FormRenderer initialValues={{ foo: 'bar' }} componentMapper={componentMapper} schema={schema}>
402+
<div />
403+
<div />
404+
</FormRenderer>
405+
);
406+
}).toThrow('FormRenderer expects only one child element!');
407+
});
408+
409+
it('should not override schema or formFields prop if explicitely given to child', () => {
410+
const ChildSpy = ({ schema, formFields }) => (
411+
<div>
412+
<div>{schema}</div>
413+
<div>{formFields}</div>
414+
</div>
415+
);
416+
render(
417+
<FormRenderer initialValues={{ foo: 'bar' }} componentMapper={componentMapper} schema={schema}>
418+
<ChildSpy schema="schema-prop" formFields="form-fields-prop" />
419+
</FormRenderer>
420+
);
421+
expect(screen.getByText('schema-prop')).toBeInTheDocument();
422+
expect(screen.getByText('form-fields-prop')).toBeInTheDocument();
423+
});
387424
});
388425
});

packages/react-renderer-demo/src/pages/components/children.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ import DocPage from '@docs/doc-page';
88

99
## Props
1010

11+
Children are supplied with `formFields` and `schema` props. If a child has one of `schema` or `formFields` prop explicitly set, the explicit props will be used.
12+
13+
```jsx
14+
15+
/**
16+
* The ChildComponent will receive schema and formFields props from the FormRenderer.
17+
*/
18+
<FormRenderer {...props}><ChildComponent/></FormRenderer>
19+
20+
21+
/**
22+
* The ChildComponent will not receive schema prop from the FormRenderer! The schema prop will be equal to "Foo".
23+
* It will still receive the formFields prop from the renderer.
24+
*/
25+
<FormRenderer {...props}><ChildComponent schema="Foo"/></FormRenderer>
26+
```
27+
1128
### formFields
1229

1330
*node*

packages/react-renderer-demo/src/pages/hooks/use-form-api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ This hook returns object containing following information:
2626

2727
If you want to render fields from a component (`tabs`, `subform`, etc.) you can use `renderForm(fields)` function.
2828

29+
## schema
30+
31+
*object*
32+
33+
The [schema prop](/components/renderer#schema) of FormRenderer.
34+
2935
## getState
3036

3137
*() => FormState*

0 commit comments

Comments
 (0)