Skip to content

Commit 636373f

Browse files
committed
Convert useState to useReducer in useFieldApi
1 parent bf5b30a commit 636373f

File tree

2 files changed

+210
-9
lines changed

2 files changed

+210
-9
lines changed

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

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useContext, useState, useRef } from 'react';
1+
import { useEffect, useContext, useRef, useReducer } from 'react';
22
import { useField } from 'react-final-form';
33
import enhancedOnChange from '../form-renderer/enhanced-on-change';
44
import RendererContext from './renderer-context';
@@ -27,12 +27,45 @@ const calculateValidate = (props, validate, component, validatorMapper) => {
2727
}
2828
};
2929

30+
const init = ({ props, validate, component, validatorMapper }) => ({
31+
initialValue: calculateInitialValue(props),
32+
arrayValidator: calculateArrayValidator(props, validate, component, validatorMapper),
33+
validate: calculateValidate(props, validate, component, validatorMapper),
34+
type: assignSpecialType(component)
35+
});
36+
37+
const reducer = (state, { type, specialType, validate, arrayValidator, initialValue }) => {
38+
switch (type) {
39+
case 'setType':
40+
return {
41+
...state,
42+
type: specialType
43+
};
44+
case 'setValidators':
45+
return {
46+
...state,
47+
validate,
48+
arrayValidator
49+
};
50+
case 'setInitialValue':
51+
return {
52+
...state,
53+
initialValue
54+
};
55+
default:
56+
return state;
57+
}
58+
};
59+
3060
const useFieldApi = ({ name, initializeOnMount, component, render, validate, ...props }) => {
3161
const { actionMapper, validatorMapper, formOptions } = useContext(RendererContext);
32-
const [initialValue, setInitialValue] = useState(() => calculateInitialValue(props));
33-
const [arrayValidator, setArrayValidator] = useState(() => calculateArrayValidator(props, validate, component, validatorMapper));
34-
const [stateValidate, setValidate] = useState(() => calculateValidate(props, validate, component, validatorMapper));
35-
const [type, setType] = useState(() => assignSpecialType(component));
62+
63+
const [{ type, initialValue, validate: stateValidate, arrayValidator }, dispatch] = useReducer(
64+
reducer,
65+
{ props, validate, component, validatorMapper },
66+
init
67+
);
68+
3669
const mounted = useRef(false);
3770

3871
const enhancedProps = {
@@ -49,16 +82,19 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, ...
4982
if (mounted.current) {
5083
const specialType = assignSpecialType(component);
5184
if (specialType !== type) {
52-
setType(specialType);
85+
dispatch({ type: 'setType', specialType });
5386
}
5487
}
5588
}, [component]);
5689

5790
/** Reinitilize array validator/validate */
5891
useEffect(() => {
5992
if (mounted.current) {
60-
setArrayValidator(calculateArrayValidator(props, validate, component, validatorMapper));
61-
setValidate(calculateValidate(props, validate, component, validatorMapper));
93+
dispatch({
94+
type: 'setValidators',
95+
validate: calculateValidate(props, validate, component, validatorMapper),
96+
arrayValidator: calculateArrayValidator(props, validate, component, validatorMapper)
97+
});
6298
}
6399
}, [validate, component, props.dataType]);
64100

@@ -67,7 +103,10 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, ...
67103
if (mounted.current) {
68104
const newInitialValue = calculateInitialValue(props);
69105
if (!isEqual(initialValue, newInitialValue)) {
70-
setInitialValue(newInitialValue);
106+
dispatch({
107+
type: 'setInitialValue',
108+
initialValue: newInitialValue
109+
});
71110
}
72111
}
73112
}, [props.initialValue, props.dataType]);
@@ -95,6 +134,7 @@ const useFieldApi = ({ name, initializeOnMount, component, render, validate, ...
95134
mounted.current = true;
96135

97136
return () => {
137+
mounted.current = false;
98138
/**
99139
* Delete the value from form state when field is inmounted
100140
*/
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import React, { Component } from 'react';
2+
import { mount } from 'enzyme';
3+
import { act } from 'react-dom/test-utils';
4+
5+
import useFieldApi from '../../files/use-field-api';
6+
import componentTypes from '../../files/component-types';
7+
import Form from '../../files/form';
8+
import RendererContext from '../../files/renderer-context';
9+
10+
describe('useFieldApi', () => {
11+
const Catcher = ({ children }) => children;
12+
13+
const TestField = (props) => {
14+
const rest = useFieldApi(props);
15+
16+
return (
17+
<Catcher {...rest}>
18+
<input {...rest.input} />
19+
</Catcher>
20+
);
21+
};
22+
23+
class WrapperComponent extends Component {
24+
render() {
25+
const { onSubmit, ...props } = this.props;
26+
return (
27+
<Form onSubmit={onSubmit}>
28+
{({ handleSubmit, form: { reset } }) => (
29+
<form onSubmit={handleSubmit}>
30+
<RendererContext.Provider
31+
value={{
32+
formOptions: {},
33+
validatorMapper: { required: () => (value) => (!value ? 'required' : undefined) }
34+
}}
35+
>
36+
<TestField reset={reset} {...props} />
37+
</RendererContext.Provider>
38+
</form>
39+
)}
40+
</Form>
41+
);
42+
}
43+
}
44+
45+
let initialProps;
46+
let onSubmit;
47+
48+
beforeEach(() => {
49+
onSubmit = jest.fn();
50+
initialProps = {
51+
name: 'some-name',
52+
component: 'text-field',
53+
onSubmit: (values) => onSubmit(values)
54+
};
55+
});
56+
57+
it('reloads type when component changes', () => {
58+
const wrapper = mount(<WrapperComponent {...initialProps} />);
59+
60+
expect(wrapper.find(Catcher).props().input.type).toEqual(undefined);
61+
62+
wrapper.setProps({ component: componentTypes.RADIO });
63+
wrapper.update();
64+
65+
expect(wrapper.find(Catcher).props().input.type).toEqual('radio');
66+
});
67+
68+
it('reloads validator when dataType changes', () => {
69+
const wrapper = mount(<WrapperComponent {...initialProps} />);
70+
71+
wrapper.find('form').simulate('submit');
72+
wrapper.update();
73+
expect(onSubmit).toHaveBeenCalledWith({});
74+
onSubmit.mockClear();
75+
76+
// validate is not checked in useField, so you have to change key too
77+
wrapper.setProps({ dataType: 'number', key: 'somekey' });
78+
wrapper.update();
79+
80+
wrapper.find('input').simulate('change', { target: { value: 'ABC' } });
81+
wrapper.update();
82+
83+
wrapper.find('form').simulate('submit');
84+
wrapper.update();
85+
expect(onSubmit).not.toHaveBeenCalled();
86+
onSubmit.mockClear();
87+
88+
expect(wrapper.find(Catcher).props().meta.error).toEqual('Values must be number');
89+
});
90+
91+
it('reloads validator when validate changes', async () => {
92+
const wrapper = mount(<WrapperComponent {...initialProps} />);
93+
94+
wrapper.find('form').simulate('submit');
95+
wrapper.update();
96+
expect(onSubmit).toHaveBeenCalledWith({});
97+
onSubmit.mockClear();
98+
99+
// validate is not checked in useField, so you have to change key too
100+
wrapper.setProps({ validate: [{ type: 'required' }], key: 'somekey' });
101+
wrapper.update();
102+
103+
await act(async () => {
104+
wrapper
105+
.find(Catcher)
106+
.props()
107+
.reset();
108+
});
109+
wrapper.update();
110+
111+
wrapper.find('form').simulate('submit');
112+
wrapper.update();
113+
expect(onSubmit).not.toHaveBeenCalled();
114+
onSubmit.mockClear();
115+
116+
expect(wrapper.find(Catcher).props().meta.error).toEqual('required');
117+
});
118+
119+
it('reloads array validator when dataType changes', () => {
120+
initialProps = {
121+
...initialProps,
122+
component: componentTypes.FIELD_ARRAY
123+
};
124+
125+
const wrapper = mount(<WrapperComponent {...initialProps} />);
126+
127+
expect(wrapper.find(Catcher).props().arrayValidator).toEqual(undefined);
128+
129+
wrapper.setProps({ dataType: 'number' });
130+
wrapper.update();
131+
132+
expect(wrapper.find(Catcher).props().arrayValidator).toEqual(expect.any(Function));
133+
});
134+
135+
it('reloads array validator when validate changes', () => {
136+
initialProps = {
137+
...initialProps,
138+
component: componentTypes.FIELD_ARRAY
139+
};
140+
141+
const wrapper = mount(<WrapperComponent {...initialProps} />);
142+
143+
expect(wrapper.find(Catcher).props().arrayValidator).toEqual(undefined);
144+
145+
wrapper.setProps({ validate: [{ type: 'required' }] });
146+
wrapper.update();
147+
148+
expect(wrapper.find(Catcher).props().arrayValidator).toEqual(expect.any(Function));
149+
});
150+
151+
it('reloads initial value', () => {
152+
const wrapper = mount(<WrapperComponent {...initialProps} />);
153+
154+
expect(wrapper.find(Catcher).props().meta.initial).toEqual(undefined);
155+
156+
wrapper.setProps({ initialValue: 'pepa' });
157+
wrapper.update();
158+
159+
expect(wrapper.find(Catcher).props().meta.initial).toEqual('pepa');
160+
});
161+
});

0 commit comments

Comments
 (0)