Skip to content

Commit 3bc386d

Browse files
authored
Merge pull request #240 from rvsia/initializeOnMount
feat(renderer): introduce initializeOnMount
2 parents 467d710 + ff79995 commit 3bc386d

File tree

5 files changed

+316
-0
lines changed

5 files changed

+316
-0
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import enhancedOnChange from './enhanced-on-change';
66
import { dataTypes } from '../constants';
77

88
class FieldProvider extends Component{
9+
componentDidMount() {
10+
if (this.props.initializeOnMount) {
11+
const initialValue = this.props.initialValue || this.props.formOptions.getFieldState(this.props.name).initial;
12+
this.props.formOptions.change(this.props.name, initialValue);
13+
}
14+
}
15+
916
componentWillUnmount(){
1017
if ((this.props.formOptions.clearOnUnmount || this.props.clearOnUnmount) && this.props.clearOnUnmount !== false) {
1118
this.props.formOptions.change(this.props.name, undefined);
@@ -56,13 +63,16 @@ FieldProvider.propTypes = {
5663
formOptions: PropTypes.shape({
5764
clearOnUnmount: PropTypes.bool,
5865
change: PropTypes.func,
66+
getFieldState: PropTypes.func,
5967
}),
6068
component: PropTypes.oneOfType(PropTypes.node, PropTypes.element, PropTypes.func),
6169
render: PropTypes.func,
6270
children: PropTypes.oneOfType(PropTypes.node, PropTypes.element, PropTypes.func),
6371
dataType: PropTypes.oneOf(Object.values(dataTypes)),
6472
name: PropTypes.string,
6573
clearOnUnmount: PropTypes.bool,
74+
initializeOnMount: PropTypes.bool,
75+
initialValue: PropTypes.any,
6676
};
6777

6878
FieldProvider.defaultProps = {

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

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import renderForm from '../../form-renderer/render-form';
77
import RendererContext from '../../form-renderer/renderer-context';
88
import { components, validators, layoutComponents } from '../../constants';
99
import FormRenderer from '../../form-renderer';
10+
import { componentTypes } from '../..';
1011

1112
describe('renderForm function', () => {
1213
let layoutMapper;
@@ -501,6 +502,7 @@ describe('renderForm function', () => {
501502
{ meta.error && <div><span>{ meta.error }</span></div> }
502503
</div>
503504
);
505+
504506
it('should add formSpy and call update function on each state change', () => {
505507
const onStateUpdate = jest.fn();
506508
const wrapper = mount(
@@ -523,6 +525,172 @@ describe('renderForm function', () => {
523525
wrapper.find('input').last().simulate('change', { target: { value: 'foovalue' }});
524526
expect(onStateUpdate).toHaveBeenCalledTimes(4);
525527
});
528+
});
529+
530+
describe('#initializeOnMount', () => {
531+
const SHOWER_FIELD = 'shower_FIELD';
532+
const INITIALIZED_FIELD = 'initialized_FIELD';
533+
const SHOW_VALUE = 'show';
534+
535+
const INITIAL_VALUE = 'some initial value';
536+
const SHOWER_FIELD_INDEX = 0;
537+
const INITIALIZED_FIELD_INDEX = 1;
538+
const NEW_VALUE = 'something different';
539+
const NOT_SHOW_VALUE = 'bla';
540+
const SCHEMA_INITIAL_VALUE = 'schema initial value';
541+
542+
const formFields = (initializeOnMount = false, initialValue) => ({
543+
fields: [{
544+
component: componentTypes.TEXT_FIELD,
545+
name: SHOWER_FIELD,
546+
}, {
547+
component: componentTypes.TEXT_FIELD,
548+
name: INITIALIZED_FIELD,
549+
initializeOnMount,
550+
initialValue,
551+
condition: {
552+
when: SHOWER_FIELD,
553+
is: SHOW_VALUE,
554+
},
555+
}],
556+
});
526557

558+
const TextField = ({ input, meta, formOptions, ...rest }) => (
559+
<div>
560+
<input { ...input } { ...rest } />
561+
</div>
562+
);
563+
564+
const updateInput = (wrapper, position, value) => {
565+
wrapper.find('input').at(position).simulate('change', { target: { value }});
566+
wrapper.update();
567+
};
568+
569+
const getFormValue = (wrapper, name) =>
570+
wrapper.find(Form).instance().form.getState().values[name];
571+
572+
const mountInitializedField = (wrapper) => updateInput(wrapper, SHOWER_FIELD_INDEX, SHOW_VALUE);
573+
const unmountInitializedField = (wrapper) => updateInput(wrapper, SHOWER_FIELD_INDEX, NOT_SHOW_VALUE);
574+
const setInitializedToNewValue = (wrapper) => updateInput(wrapper, INITIALIZED_FIELD_INDEX, NEW_VALUE);
575+
const expectNewValue = (wrapper) => expect(getFormValue(wrapper, INITIALIZED_FIELD)).toEqual(NEW_VALUE);
576+
const expectInitialValue = (wrapper) => expect(getFormValue(wrapper, INITIALIZED_FIELD)).toEqual(INITIAL_VALUE);
577+
const expectSchemaInitialValue = (wrapper) => expect(getFormValue(wrapper, INITIALIZED_FIELD)).toEqual(SCHEMA_INITIAL_VALUE);
578+
579+
it('should reset value after mount when set on fields', () => {
580+
const SET_INITIALIZE_ON_MOUNT = true;
581+
582+
const wrapper = mount(
583+
<FormRenderer
584+
layoutMapper={ layoutMapper }
585+
formFieldsMapper={{
586+
[components.TEXT_FIELD]: TextField,
587+
}}
588+
schema={ formFields(SET_INITIALIZE_ON_MOUNT) }
589+
onSubmit={ jest.fn() }
590+
initialValues={{
591+
[INITIALIZED_FIELD]: INITIAL_VALUE,
592+
}}
593+
/>
594+
);
595+
596+
expectInitialValue(wrapper);
597+
598+
mountInitializedField(wrapper);
599+
setInitializedToNewValue(wrapper);
600+
601+
expectNewValue(wrapper);
602+
603+
unmountInitializedField(wrapper);
604+
expectNewValue(wrapper);
605+
606+
mountInitializedField(wrapper);
607+
expectInitialValue(wrapper);
608+
});
609+
610+
it('should not reset value after mount when set on fields', () => {
611+
const UNSET_INITIALIZE_ON_MOUNT = false;
612+
613+
const wrapper = mount(
614+
<FormRenderer
615+
layoutMapper={ layoutMapper }
616+
formFieldsMapper={{
617+
[components.TEXT_FIELD]: TextField,
618+
}}
619+
schema={ formFields(UNSET_INITIALIZE_ON_MOUNT) }
620+
onSubmit={ jest.fn() }
621+
initialValues={{
622+
[INITIALIZED_FIELD]: INITIAL_VALUE,
623+
}}
624+
/>
625+
);
626+
627+
expectInitialValue(wrapper);
628+
629+
mountInitializedField(wrapper);
630+
setInitializedToNewValue(wrapper);
631+
632+
expectNewValue(wrapper);
633+
634+
unmountInitializedField(wrapper);
635+
expectNewValue(wrapper);
636+
637+
mountInitializedField(wrapper);
638+
expectNewValue(wrapper);
639+
});
640+
641+
it('should reset value after mount when set on fields and use initialValue from schema instead of renderer initialValues', () => {
642+
const SET_INITIALIZE_ON_MOUNT = true;
643+
644+
const wrapper = mount(
645+
<FormRenderer
646+
layoutMapper={ layoutMapper }
647+
formFieldsMapper={{
648+
[components.TEXT_FIELD]: TextField,
649+
}}
650+
schema={ formFields(SET_INITIALIZE_ON_MOUNT, SCHEMA_INITIAL_VALUE) }
651+
onSubmit={ jest.fn() }
652+
initialValues={{
653+
[INITIALIZED_FIELD]: INITIAL_VALUE,
654+
}}
655+
/>
656+
);
657+
658+
mountInitializedField(wrapper);
659+
setInitializedToNewValue(wrapper);
660+
661+
expectNewValue(wrapper);
662+
663+
unmountInitializedField(wrapper);
664+
expectNewValue(wrapper);
665+
666+
mountInitializedField(wrapper);
667+
expectSchemaInitialValue(wrapper);
668+
});
669+
670+
it('should reset value after mount when set on fields and use initialValue from schema', () => {
671+
const SET_INITIALIZE_ON_MOUNT = true;
672+
673+
const wrapper = mount(
674+
<FormRenderer
675+
layoutMapper={ layoutMapper }
676+
formFieldsMapper={{
677+
[components.TEXT_FIELD]: TextField,
678+
}}
679+
schema={ formFields(SET_INITIALIZE_ON_MOUNT, SCHEMA_INITIAL_VALUE) }
680+
onSubmit={ jest.fn() }
681+
/>
682+
);
683+
684+
mountInitializedField(wrapper);
685+
setInitializedToNewValue(wrapper);
686+
687+
expectNewValue(wrapper);
688+
689+
unmountInitializedField(wrapper);
690+
expectNewValue(wrapper);
691+
692+
mountInitializedField(wrapper);
693+
expectSchemaInitialValue(wrapper);
694+
});
527695
});
528696
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import Grid from '@material-ui/core/Grid'
2+
import RouterLink from 'next/link';
3+
import Link from '@material-ui/core/Link';
4+
import RawComponent from '@docs/raw-component';
5+
6+
import ListOfContents from '../../src/helpers/list-of-contents';
7+
8+
<Grid container item>
9+
<Grid item xs={12} md={10}>
10+
11+
# Initialize On Mount
12+
13+
Data Driven Forms provides a way how you can easily initialized a field when the field is mounted (re-mounted).
14+
15+
Just pass a `initializeOnMount` prop and set it to `true`.
16+
17+
The field will use the `initialValue` set in the schema (<RouterLink href="/renderer/component-api#formgroupwrappedcomponents"><Link>initialValue</Link></RouterLink>) or in the renderer (<RouterLink href="/renderer/renderer-api#optionalprops"><Link>initialValues</Link></RouterLink>).
18+
19+
`initialValue` has higher priority than a value from `initialValues`.
20+
21+
## Example
22+
23+
24+
```jsx
25+
{
26+
component: componentTypes.TEXT_FIELD,
27+
name: 'name',
28+
initializeOnMount: true,
29+
initialValue: 'this value will be set'
30+
}
31+
```
32+
33+
## When to use it?
34+
35+
This feature comes handy if you need change a value when an user traverses a form, which shows and hides fields, and the value is not set by the user. Very useful case is used it wizard forms, where you can set different value for the same input according the way the user went in the wizard form by using this option combined with <RouterLink href="/renderer/component-api#commonpropsforallformfields"><Link>hideField</Link></RouterLink> prop.
36+
37+
<RawComponent source="initialize-mount" />
38+
39+
## Clear the value
40+
41+
If you need clear the value after unmounting, you can do it by using <RouterLink href="/renderer/unmounting"><Link>clearOnUnmount</Link></RouterLink>.
42+
43+
</Grid>
44+
<Grid item xs={false} md={2}>
45+
<ListOfContents file="renderer/initialize-mount" />
46+
</Grid>
47+
</Grid>

packages/react-renderer-demo/src/app/src/components/navigation/documentation-pages.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export const docs = [{
2222
}, {
2323
component: 'unmounting',
2424
linkText: 'Clear On Unmount',
25+
}, {
26+
component: 'initialize-mount',
27+
linkText: 'Initialize On Mount',
2528
}, {
2629
component: 'validators',
2730
linkText: 'Validators',
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { useState } from 'react';
2+
import FormRenderer, { componentTypes } from '@data-driven-forms/react-form-renderer';
3+
import { layoutMapper, formFieldsMapper } from '@data-driven-forms/pf4-component-mapper';
4+
import { Title } from '@patternfly/react-core';
5+
6+
const schema = {
7+
fields: [{
8+
component: componentTypes.WIZARD,
9+
name: 'wizard',
10+
fields: [
11+
{
12+
title: 'Choose your way',
13+
name: 'step-1',
14+
stepKey: 1,
15+
nextStep: {
16+
when: 'selection',
17+
stepMapper: {
18+
'way-1': 'way-1',
19+
'way-2': 'way-2',
20+
},
21+
},
22+
fields: [
23+
{
24+
component: componentTypes.SELECT,
25+
name: 'selection',
26+
label: 'Select your way',
27+
isRequired: true,
28+
options: [
29+
{ label: 'Please choose your way' },
30+
{ value: 'way-1', label: 'way-1' },
31+
{ value: 'way-2', label: 'way-2' },
32+
],
33+
validate: [{ type: 'required-validator' }],
34+
},
35+
],
36+
},
37+
{
38+
title: 'Way 1',
39+
stepKey: 'way-1',
40+
fields: [
41+
{
42+
component: componentTypes.TEXT_FIELD,
43+
initializeOnMount: true,
44+
hideField: true,
45+
name: 'chosen-way',
46+
initialValue: 'User chose the first way',
47+
},
48+
],
49+
},
50+
{
51+
title: 'Way 2',
52+
stepKey: 'way-2',
53+
fields: [
54+
{
55+
component: componentTypes.TEXT_FIELD,
56+
initializeOnMount: true,
57+
hideField: true,
58+
name: 'chosen-way',
59+
initialValue: 'User chose the second way',
60+
},
61+
],
62+
},
63+
]},
64+
],
65+
};
66+
67+
const InitializeOnMountWizardExample = () => {
68+
const [ values, setValues ] = useState({});
69+
return (
70+
<div className="pf4">
71+
<FormRenderer
72+
layoutMapper={ layoutMapper }
73+
formFieldsMapper={ formFieldsMapper }
74+
schema={ schema }
75+
onSubmit={ console.log }
76+
onStateUpdate={ ({ values }) => setValues(values) }
77+
showFormControls={ false }
78+
/>
79+
<div style={{ marginTop: 16 }}>
80+
<Title size="md">Form values</Title>
81+
<pre>
82+
{ JSON.stringify(values, null, 2) }
83+
</pre>
84+
</div>
85+
</div>
86+
);};
87+
88+
export default InitializeOnMountWizardExample;

0 commit comments

Comments
 (0)