Skip to content

Commit 43b9dec

Browse files
authored
fix: fix state updates by mutators (#141)
1 parent 7016761 commit 43b9dec

File tree

6 files changed

+158
-21
lines changed

6 files changed

+158
-21
lines changed

src/lib/core/components/Form/Controller.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import React from 'react';
2-
31
import _ from 'lodash';
42

53
import {Spec} from '../../types';
64

7-
import {EMPTY_MUTATOR} from './constants';
85
import {
96
useComponents,
107
useControllerMirror,
118
useDynamicFormsCtx,
129
useField,
1310
useRender,
1411
useSearch,
12+
useSpec,
1513
useValidate,
1614
} from './hooks';
1715
import {ControllerMirror, FieldValue, ValidateError} from './types';
@@ -38,17 +36,7 @@ export const Controller = <Value extends FieldValue, SpecType extends Spec>({
3836
parentOnUnmount,
3937
}: ControllerProps<Value, SpecType>) => {
4038
const {tools, mutators, __mirror} = useDynamicFormsCtx();
41-
42-
const spec = React.useMemo(() => {
43-
const specMutator = _.get(mutators.spec, name, EMPTY_MUTATOR);
44-
45-
if (specMutator !== EMPTY_MUTATOR) {
46-
return _.merge(_spec, specMutator);
47-
}
48-
49-
return _spec;
50-
}, [_spec, mutators.spec, name]);
51-
39+
const spec = useSpec({name, spec: _spec, mutators});
5240
const {inputEntity, Layout} = useComponents(spec);
5341
const render = useRender({name, spec, inputEntity, Layout});
5442
const validate = useValidate(spec);
@@ -57,6 +45,7 @@ export const Controller = <Value extends FieldValue, SpecType extends Spec>({
5745
initialValue: _.get(tools.initialValue, name),
5846
value,
5947
spec,
48+
originalSpec: _spec,
6049
validate,
6150
tools,
6251
parentOnChange,

src/lib/core/components/Form/hooks/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ export * from './useMonaco';
1616
export * from './useSearchStore';
1717
export * from './useSearchContext';
1818
export * from './useSearch';
19+
export * from './useSpec';
1920
export * from './useCreateSearchContext';

src/lib/core/components/Form/hooks/useField.tsx

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22

33
import _ from 'lodash';
44

5-
import {isArraySpec, isNumberSpec, isObjectSpec} from '../../../helpers';
5+
import {isArraySpec, isCorrectSpec, isNumberSpec, isObjectSpec} from '../../../helpers';
66
import {Spec} from '../../../types';
77
import {EMPTY_MUTATOR, OBJECT_ARRAY_CNT, OBJECT_ARRAY_FLAG} from '../constants';
88
import {
@@ -13,18 +13,23 @@ import {
1313
FieldRenderProps,
1414
FieldValue,
1515
ValidateError,
16+
ValidatorsMap,
1617
} from '../types';
1718
import {
1819
isArrayItem,
20+
isCorrectConfig,
1921
isErrorMutatorCorrect,
2022
isValueMutatorCorrect,
2123
transformArrIn,
2224
transformArrOut,
2325
} from '../utils';
2426

27+
import {useDynamicFormsCtx} from './useDynamicFormsCtx';
28+
2529
export interface UseFieldProps<Value extends FieldValue, SpecType extends Spec> {
2630
name: string;
2731
spec: SpecType;
32+
originalSpec: SpecType;
2833
initialValue: Value;
2934
value: Value;
3035
validate?: (value?: Value) => ValidateError;
@@ -39,10 +44,58 @@ export interface UseFieldProps<Value extends FieldValue, SpecType extends Spec>
3944
parentOnUnmount: ((childName: string) => void) | null;
4045
mutators: DynamicFormMutators;
4146
}
47+
// TODO: remove
48+
const useExtraValidator = <Value extends FieldValue, SpecType extends Spec>({
49+
name,
50+
spec,
51+
}: {
52+
name: string;
53+
spec: SpecType;
54+
}) => {
55+
const {mutators, config} = useDynamicFormsCtx();
56+
57+
const nextSpec = (() => {
58+
const specMutator = _.get(mutators.spec, name, EMPTY_MUTATOR);
59+
60+
if (specMutator !== EMPTY_MUTATOR) {
61+
return _.merge(_.cloneDeep(spec), specMutator);
62+
}
63+
64+
return spec;
65+
})();
66+
const validator = (() => {
67+
if (isCorrectConfig(config) && isCorrectSpec(nextSpec)) {
68+
const {validators} = config[nextSpec.type] as unknown as {
69+
validators: ValidatorsMap<Value, SpecType>;
70+
};
71+
72+
if (validators) {
73+
if (
74+
(!_.isString(nextSpec.validator) || !nextSpec.validator.length) &&
75+
_.isFunction(validators.base)
76+
) {
77+
return (value?: Value) => validators.base(nextSpec, value);
78+
}
79+
80+
if (
81+
_.isString(nextSpec.validator) &&
82+
_.isFunction(validators[nextSpec.validator])
83+
) {
84+
return (value?: Value) => validators[nextSpec.validator!]!(nextSpec, value);
85+
}
86+
}
87+
}
88+
89+
return;
90+
})();
91+
92+
return validator;
93+
};
4294

4395
export const useField = <Value extends FieldValue, SpecType extends Spec>({
4496
name,
4597
spec,
98+
originalSpec,
4699
initialValue,
47100
value: externalValue,
48101
validate: propsValidate,
@@ -52,6 +105,7 @@ export const useField = <Value extends FieldValue, SpecType extends Spec>({
52105
mutators,
53106
}: UseFieldProps<Value, SpecType>): FieldRenderProps<Value> => {
54107
const firstRenderRef = React.useRef(true);
108+
const extraValidator = useExtraValidator<Value, SpecType>({name, spec: originalSpec});
55109

56110
const validate = React.useCallback(
57111
(value: Value) => propsValidate?.(transformArrOut(value)),
@@ -112,10 +166,12 @@ export const useField = <Value extends FieldValue, SpecType extends Spec>({
112166
valOrSetter: Value | ((currentValue: Value) => Value),
113167
childErrors?: Record<string, ValidateError>,
114168
errorMutator?: ValidateError,
169+
extraValidator?: ReturnType<typeof useExtraValidator<Value, SpecType>>,
115170
) => {
116171
setState((state) => {
117172
const _value = _.isFunction(valOrSetter) ? valOrSetter(state.value) : valOrSetter;
118-
const error = validate?.(_value) || errorMutator;
173+
const error =
174+
(extraValidator ? extraValidator(_value) : validate?.(_value)) || errorMutator;
119175
let value = transformArrIn(_value);
120176

121177
if (isNumberSpec(spec) && !error) {
@@ -298,7 +354,7 @@ export const useField = <Value extends FieldValue, SpecType extends Spec>({
298354
valueMutator !== state.value &&
299355
valueMutator !== EMPTY_MUTATOR
300356
) {
301-
onLocalChange(valueMutator, undefined, errorMutator);
357+
onLocalChange(valueMutator, undefined, errorMutator, extraValidator);
302358
} else if (state.error !== errorMutator && !(state.error && !errorMutator)) {
303359
setState({...state, error: errorMutator});
304360
(parentOnChange ? parentOnChange : tools.onChange)(name, state.value, {

src/lib/core/components/Form/hooks/useMutators.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,45 @@ import React from 'react';
22

33
import _ from 'lodash';
44

5-
import {DynamicFormMutators} from '../types';
5+
import {DynamicFormMutators, SpecMutator} from '../types';
66

77
export const useMutators = (externalMutators?: DynamicFormMutators) => {
88
const firstRenderRef = React.useRef(true);
99
const [store, setStore] = React.useState<DynamicFormMutators>(externalMutators || {});
1010

1111
const mutateDFState = React.useCallback(
1212
(mutators: DynamicFormMutators) => {
13+
const mergeSpec = (a: Record<string, SpecMutator>, b: Record<string, SpecMutator>) => {
14+
const result = _.cloneDeep(a);
15+
16+
const getKeys = (parent: any): string[][] => {
17+
if (_.isObjectLike(parent)) {
18+
return _.keys(parent).reduce((acc: string[][], parentKey) => {
19+
const childKeys = getKeys(parent[parentKey]);
20+
21+
return [
22+
...acc,
23+
...(childKeys.length ? [] : [[parentKey]]),
24+
...childKeys.map((childKey) => [parentKey, ...childKey]),
25+
];
26+
}, []);
27+
}
28+
29+
return [];
30+
};
31+
32+
getKeys(b).forEach((key) => {
33+
_.set(result, key, _.get(b, key));
34+
});
35+
36+
return result;
37+
};
38+
1339
setStore((store) => ({
1440
...store,
1541
...(mutators.errors ? {errors: _.merge(store.errors, mutators.errors)} : {}),
1642
...(mutators.values ? {values: _.merge(store.values, mutators.values)} : {}),
17-
...(mutators.spec ? {spec: _.merge(store.spec, mutators.spec)} : {}),
43+
...(mutators.spec ? {spec: mergeSpec(store.spec || {}, mutators.spec)} : {}),
1844
}));
1945
},
2046
[setStore],
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
3+
import _ from 'lodash';
4+
5+
import {Spec} from '../../../types';
6+
import {EMPTY_MUTATOR} from '../constants';
7+
import {DynamicFormMutators} from '../types';
8+
9+
export interface UseSpecParams {
10+
name: string;
11+
spec: Spec;
12+
mutators: DynamicFormMutators;
13+
}
14+
15+
export const useSpec = ({spec: _spec, mutators, name}: UseSpecParams) => {
16+
const firstRenderRef = React.useRef(true);
17+
const [spec, setSpec] = React.useState(() => {
18+
const specMutator = _.get(mutators.spec, name, EMPTY_MUTATOR);
19+
20+
if (specMutator !== EMPTY_MUTATOR) {
21+
return _.merge(_.cloneDeep(_spec), specMutator);
22+
}
23+
24+
return _spec;
25+
});
26+
27+
React.useEffect(() => {
28+
if (!firstRenderRef.current) {
29+
const specMutator = _.get(mutators.spec, name, EMPTY_MUTATOR);
30+
31+
if (specMutator === EMPTY_MUTATOR) {
32+
if (!_.isEqual(spec, _spec)) {
33+
setSpec(_spec);
34+
}
35+
} else {
36+
const nextSpec = _.merge(_.cloneDeep(_spec), specMutator);
37+
38+
if (!_.isEqual(spec, nextSpec)) {
39+
setSpec(nextSpec);
40+
}
41+
}
42+
}
43+
}, [_spec, mutators, name]);
44+
45+
React.useEffect(() => {
46+
firstRenderRef.current = false;
47+
}, []);
48+
49+
return spec;
50+
};
Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
1-
import {FormValue, Spec} from '../../../types';
1+
import {
2+
ArraySpec,
3+
BooleanSpec,
4+
FormValue,
5+
NumberSpec,
6+
ObjectSpec,
7+
StringSpec,
8+
} from '../../../types';
29

310
import {BaseValidateError} from './';
411

12+
export type SpecMutator = Partial<
13+
| (Omit<ArraySpec, 'viewSpec'> & {viewSpec: Partial<ArraySpec['viewSpec']>})
14+
| (Omit<BooleanSpec, 'viewSpec'> & {viewSpec: Partial<BooleanSpec['viewSpec']>})
15+
| (Omit<NumberSpec, 'viewSpec'> & {viewSpec: Partial<NumberSpec['viewSpec']>})
16+
| (Omit<ObjectSpec, 'viewSpec'> & {viewSpec: Partial<ObjectSpec['viewSpec']>})
17+
| (Omit<StringSpec, 'viewSpec'> & {viewSpec: Partial<StringSpec['viewSpec']>})
18+
>;
19+
520
export interface DynamicFormMutators {
621
errors?: Record<string, BaseValidateError>;
722
values?: Record<string, FormValue>;
8-
spec?: Record<string, Partial<Spec>>;
23+
spec?: Record<string, SpecMutator>;
924
}

0 commit comments

Comments
 (0)