Skip to content

Commit b968f04

Browse files
committed
feat(renderer): introduce FieldProps
1 parent a59955a commit b968f04

File tree

7 files changed

+95
-37
lines changed

7 files changed

+95
-37
lines changed

packages/common/src/dual-list-select/dual-list-select.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ const DualListSelectCommon = (props) => {
3333

3434
const { DualListSelect, ...rest } = useFieldApi({
3535
...props,
36-
isEqual: (current, initial) => isEqual([...(current || [])].sort(), [...(initial || [])].sort())
36+
FieldProps: {
37+
isEqual: (current, initial) => isEqual([...(current || [])].sort(), [...(initial || [])].sort())
38+
}
3739
});
3840

3941
const leftValues = rest.options

packages/pf4-component-mapper/src/dual-list-select/dual-list-select.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ const DualList = (props) => {
2626
...rest
2727
} = useFieldApi({
2828
...props,
29-
isEqual: (current, initial) => isEqual([...(current || [])].sort(), [...(initial || [])].sort())
29+
FieldProps: {
30+
isEqual: (current, initial) => isEqual([...(current || [])].sort(), [...(initial || [])].sort())
31+
}
3032
});
3133

3234
const [sortConfig, setSortConfig] = useState(() => ({ left: isSortable && 'asc', right: isSortable && 'asc' }));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1650,7 +1650,7 @@ describe('renderForm function', () => {
16501650

16511651
expect(wrapper.find('label').text()).toEqual(label);
16521652
expect(resolveProps).toHaveBeenCalledWith(
1653-
{ label: 'standard label' },
1653+
{ label: 'standard label', component: 'custom-component' },
16541654
expect.objectContaining({ meta: expect.any(Object), input: expect.any(Object) }),
16551655
expect.any(Object)
16561656
);

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,19 @@ describe('useFieldApi', () => {
215215
wrapper.update();
216216
expect(wrapper.find('input')).toHaveLength(1);
217217
});
218+
219+
it('omits FieldProps', () => {
220+
const parse = jest.fn().mockImplementation((value) => value);
221+
initialProps = {...initialProps, FieldProps: { parse }};
222+
223+
const wrapper = mount(<WrapperComponent {...initialProps} />);
224+
225+
expect(wrapper.find(Catcher).props().FieldProps).toEqual(undefined);
226+
expect(parse.mock.calls).toHaveLength(0);
227+
228+
wrapper.find('input').simulate('change', { target: { value: 'ABC' } });
229+
wrapper.update();
230+
231+
expect(parse.mock.calls).toHaveLength(1);
232+
});
218233
});

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

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import composeValidators from './compose-validators';
1010
import isEqual from 'lodash/isEqual';
1111

1212
const calculateInitialValue = (props) => {
13-
if (Object.prototype.hasOwnProperty.call(props, 'initialValue') && Object.prototype.hasOwnProperty.call(props, 'dataType')) {
13+
if (Object.prototype.hasOwnProperty.call(props, 'initialValue') && props.dataType) {
1414
return convertInitialValue(props.initialValue, props.dataType);
1515
}
1616
};
@@ -84,33 +84,53 @@ const createFieldProps = (name, formOptions) => {
8484
};
8585
};
8686

87-
const useFieldApi = ({ name, initializeOnMount, component, render, validate, resolveProps, useWarnings, ...props }) => {
87+
const useFieldApi = ({
88+
name,
89+
resolveProps,
90+
...props
91+
}) => {
8892
const { validatorMapper, formOptions } = useContext(RendererContext);
8993
const [warning, setWarning] = useState();
9094

91-
const { validate: resolvePropsValidate, ...resolvedProps } = resolveProps
95+
const resolvedProps = resolveProps
9296
? resolveProps(props, createFieldProps(name, formOptions), formOptions) || {}
9397
: {};
9498

95-
const finalValidate = resolvePropsValidate || validate;
99+
const combinedProps = { ...props, ...resolvedProps};
100+
const {
101+
initializeOnMount,
102+
component,
103+
render,
104+
validate,
105+
useWarnings,
106+
clearOnUnmount,
107+
dataType,
108+
FieldProps,
109+
...rest
110+
} = combinedProps;
96111

97112
const [{ type, initialValue, validate: stateValidate, arrayValidator }, dispatch] = useReducer(
98113
reducer,
99-
{ props: { ...props, ...resolvedProps }, validate: finalValidate, component, validatorMapper, setWarning, useWarnings },
114+
{ props: combinedProps, validate, component, validatorMapper, setWarning, useWarnings },
100115
init
101116
);
102117

103118
const mounted = useRef(false);
104119

105120
const enhancedProps = {
106-
type,
107-
...props,
108-
...resolvedProps,
121+
dataType,
122+
type: combinedProps.type,
123+
...(Object.prototype.hasOwnProperty.call(combinedProps, 'initialValue')
124+
? {initialValue: combinedProps.initialValue}
125+
: {}
126+
),
127+
...FieldProps,
128+
...(type ? { type } : {}),
109129
...(initialValue ? { initialValue } : {}),
110130
...(stateValidate ? { validate: stateValidate } : {})
111131
};
112132

113-
const fieldProps = useField(name, enhancedProps);
133+
const field = useField(name, enhancedProps);
114134

115135
/** Reinitilize type */
116136
useEffect(() => {
@@ -127,8 +147,8 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, res
127147
if (mounted.current) {
128148
dispatch({
129149
type: 'setValidators',
130-
validate: calculateValidate(enhancedProps, finalValidate, component, validatorMapper, setWarning, useWarnings),
131-
arrayValidator: calculateArrayValidator(enhancedProps, finalValidate, component, validatorMapper)
150+
validate: calculateValidate(enhancedProps, validate, component, validatorMapper, setWarning, useWarnings),
151+
arrayValidator: calculateArrayValidator(enhancedProps, validate, component, validatorMapper)
132152
});
133153
}
134154
/**
@@ -137,7 +157,7 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, res
137157
* Using stringify is acceptable here since the array is usually very small.
138158
* If we notice performance hit, we can implement custom hook with a deep equal functionality.
139159
*/
140-
}, [finalValidate ? JSON.stringify(finalValidate) : false, component, enhancedProps.dataType]);
160+
}, [validate ? JSON.stringify(validate) : false, component, dataType]);
141161

142162
/** Re-convert initialValue when changed */
143163
useEffect(() => {
@@ -150,7 +170,7 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, res
150170
});
151171
}
152172
}
153-
}, [enhancedProps.initialValue, enhancedProps.dataType]);
173+
}, [enhancedProps.initialValue, dataType]);
154174

155175
useEffect(() => {
156176
/**
@@ -161,33 +181,33 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, res
161181
const value = Object.prototype.hasOwnProperty.call(enhancedProps, 'initialValue')
162182
? enhancedProps.initialValue
163183
: formOptions.getFieldState(name).initial;
164-
fieldProps.input.onChange(value);
184+
field.input.onChange(value);
165185
}
166-
}, [initializeOnMount, enhancedProps.initialValue, fieldProps.meta.initial, props.dataType]);
186+
}, [initializeOnMount, enhancedProps.initialValue, field.meta.initial, dataType]);
167187

168188
/**
169189
* Prepare deleted value of field
170190
*/
171-
const fieldClearedValue = Object.prototype.hasOwnProperty.call(props, 'clearedValue') ? props.clearedValue : formOptions.clearedValue;
191+
const fieldClearedValue = Object.prototype.hasOwnProperty.call(rest, 'clearedValue') ? rest.clearedValue : formOptions.clearedValue;
172192

173193
useEffect(
174194
() => {
175195
mounted.current = true;
176-
if (fieldProps.input.type === 'file') {
177-
formOptions.registerInputFile(fieldProps.input.name);
196+
if (field.input.type === 'file') {
197+
formOptions.registerInputFile(field.input.name);
178198
}
179199

180200
return () => {
181201
mounted.current = false;
182202
/**
183203
* Delete the value from form state when field is inmounted
184204
*/
185-
if ((formOptions.clearOnUnmount || props.clearOnUnmount) && props.clearOnUnmount !== false) {
186-
fieldProps.input.onChange(fieldClearedValue);
205+
if ((formOptions.clearOnUnmount || clearOnUnmount) && clearOnUnmount !== false) {
206+
field.input.onChange(fieldClearedValue);
187207
}
188208

189-
if (fieldProps.input.type === 'file') {
190-
formOptions.unRegisterInputFile(fieldProps.input.name);
209+
if (field.input.type === 'file') {
210+
formOptions.unRegisterInputFile(field.input.name);
191211
}
192212
};
193213
},
@@ -197,38 +217,33 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, res
197217

198218
const {
199219
initialValue: _initialValue,
200-
clearOnUnmount,
201-
dataType,
202220
clearedValue,
203-
isEqual: _isEqual,
204-
validate: _validate,
205-
type: _type,
206221
...cleanProps
207-
} = enhancedProps;
222+
} = rest;
208223

209224
/**
210225
* construct component props necessary that would live in field provider
211226
*/
212227
return {
213228
...cleanProps,
214-
...fieldProps,
229+
...field,
215230
...(arrayValidator && { arrayValidator }),
216231
...(useWarnings && {
217232
meta: {
218-
...fieldProps.meta,
233+
...field.meta,
219234
warning
220235
}
221236
}),
222237
input: {
223-
...fieldProps.input,
238+
...field.input,
224239
value:
225-
fieldProps.input.type === 'file' && typeof fieldProps.input.value === 'object' ? fieldProps.input.value.inputValue : fieldProps.input.value,
240+
field.input.type === 'file' && typeof field.input.value === 'object' ? field.input.value.inputValue : field.input.value,
226241
onChange: (...args) => {
227242
enhancedOnChange(
228243
{
229-
...fieldProps.meta,
244+
...field.meta,
230245
dataType,
231-
onChange: fieldProps.input.onChange,
246+
onChange: field.input.onChange,
232247
clearedValue: fieldClearedValue
233248
},
234249
...args

packages/react-renderer-demo/src/pages/migration-guide-v3.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,19 @@ FormRenderer component is no longer a default export of the `react-form-renderer
6666

6767
All the mappers except Ant Design and PatternFly 4 were migrated to use JSS (CSS in JS), so you don't have to use any css-loader. (However, you still need to setup the loader in case you are using ant-component-mapper of pf4-component-mapper. If you are using PF4, you are probably using the loader right now, as you need it also for the core package.)
6868

69+
## FieldProps introduction
70+
71+
A new [FieldProps](/schema/introduction#fieldprops) props is introduced. This object serves to pass values to Final Form [useField configuration](https://final-form.org/docs/react-final-form/types/FieldProps). Do not use for natively supported props (`initialValue`, `validate`, ...).
72+
73+
```diff
74+
{
75+
name: 'component-with-complex-isEqual',
76+
component: 'dual-list-selector',
77+
- isEqual: (a, b) => isEqual(a, b),
78+
+ FieldProps: {
79+
+ isEqual: (a, b) => isEqual(a, b),
80+
+ }
81+
}
82+
```
83+
6984
</DocPage>

packages/react-renderer-demo/src/pages/schema/introduction.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Other attribues, such as title or description, can be used in [form templates](/
2929
clearOnUnmount: true,
3030
condition: { ... },
3131
dataType: 'string',
32+
FieldProps: { ... },
3233
hideField: true,
3334
initializeOnMount: true,
3435
initialValue: 'default-login',
@@ -132,6 +133,14 @@ Data type sets the type the value will be converted to. Read more [here](/schema
132133

133134
---
134135

136+
### FieldProps
137+
138+
*object*
139+
140+
You can pass additional [Final Form FieldProps](https://final-form.org/docs/react-final-form/types/FieldProps) via FieldProps object. This prop is made to avoid conflicts between Final Form props and component props.
141+
142+
---
143+
135144
### hideField
136145

137146
*bool*

0 commit comments

Comments
 (0)