Skip to content

Commit c343cc0

Browse files
authored
fix(@uform/core/react): fix #613 #615 (#618)
* fix(@uform/shared): fix isValid * fix(@uform/core): fix visible changed but not trigger onChange callback * fix(@uform/core): fix #613 and #615 * refactor(@uform/core): improve code
1 parent b7fde9c commit c343cc0

File tree

16 files changed

+360
-80
lines changed

16 files changed

+360
-80
lines changed

packages/core/src/__tests__/__snapshots__/index.spec.ts.snap

+109
Large diffs are not rendered by default.

packages/core/src/__tests__/index.spec.ts

+48-4
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,33 @@ describe('createForm', () => {
111111
expect(form.getFormGraph()).toMatchSnapshot()
112112
})
113113

114+
const sleep = (d=1000)=>new Promise((resolve)=>{
115+
setTimeout(()=>{
116+
resolve()
117+
},d)
118+
})
119+
120+
test('invalid initialValue will not trigger validate', async () => {
121+
const form = createForm()
122+
const field = form.registerField({
123+
name: 'aa',
124+
rules:[{
125+
required:true
126+
}]
127+
})
128+
const mutators = form.createMutators(field)
129+
field.subscribe(() => {
130+
mutators.validate({ throwErrors: false })
131+
})
132+
form.setFormState(state => {
133+
state.initialValues = {
134+
aa: null
135+
}
136+
})
137+
await sleep(10)
138+
expect(field.getState(state=>state.errors).length).toEqual(1)
139+
})
140+
114141
test('lifecycles', () => {
115142
const onFormInit = jest.fn()
116143
const onFieldInit = jest.fn()
@@ -437,9 +464,9 @@ describe('clearErrors', () => {
437464
expect(form.getFormState(state => state.errors)).toEqual([])
438465
})
439466

440-
test('wildcard path', async () => { })
467+
test('wildcard path', async () => {})
441468

442-
test('effect', async () => { })
469+
test('effect', async () => {})
443470
})
444471

445472
describe('validate', () => {
@@ -487,7 +514,7 @@ describe('validate', () => {
487514

488515
try {
489516
await form.submit()
490-
} catch (e) { }
517+
} catch (e) {}
491518
expect(onValidateFailedTrigger).toBeCalledTimes(1)
492519
})
493520

@@ -515,7 +542,7 @@ describe('validate', () => {
515542
}) // CustomValidator error
516543
try {
517544
await form.submit()
518-
} catch (e) { }
545+
} catch (e) {}
519546
expect(onValidateFailedTrigger).toBeCalledTimes(1)
520547
})
521548

@@ -1560,6 +1587,23 @@ describe('major sences', () => {
15601587
expect(form.getFormGraph()).toMatchSnapshot()
15611588
})
15621589

1590+
test('visible onChange', () => {
1591+
const onChangeHandler = jest.fn()
1592+
const form = createForm({
1593+
initialValues: {
1594+
aa: 123
1595+
},
1596+
onChange: onChangeHandler
1597+
})
1598+
form.registerField({
1599+
name: 'aa'
1600+
})
1601+
form.setFieldState('aa', state => {
1602+
state.visible = false
1603+
})
1604+
expect(onChangeHandler).toBeCalledTimes(1)
1605+
})
1606+
15631607
test('deep nested visible(root)', () => {
15641608
const form = createForm()
15651609
form.registerField({ path: 'aa', value: {} })

packages/core/src/index.ts

+66-31
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ export function createForm<FieldProps, VirtualFieldProps>(
9696
state.initialValue = initialValue
9797
if (!isValid(state.value)) {
9898
state.value = initialValue
99+
} else if (
100+
/array/gi.test(state.dataType) &&
101+
state.value &&
102+
state.value.length === 0
103+
) {
104+
state.value = initialValue
99105
}
100106
}
101107
}
@@ -112,7 +118,7 @@ export function createForm<FieldProps, VirtualFieldProps>(
112118
const updateFields = (field: IField | IVirtualField) => {
113119
if (isField(field)) {
114120
field.setState(state => {
115-
if (state.visible) {
121+
if (state.visible || state.unmounted) {
116122
if (valuesChanged) {
117123
syncFieldValues(state)
118124
}
@@ -122,14 +128,14 @@ export function createForm<FieldProps, VirtualFieldProps>(
122128
} else {
123129
//缓存变化,等字段重新显示的时候再执行
124130
if (valuesChanged) {
125-
env.visiblePendingFields[state.name] =
126-
env.visiblePendingFields[state.name] || {}
127-
env.visiblePendingFields[state.name].values = true
131+
env.hiddenPendingFields[state.name] =
132+
env.hiddenPendingFields[state.name] || {}
133+
env.hiddenPendingFields[state.name].values = true
128134
}
129135
if (initialValuesChanged) {
130-
env.visiblePendingFields[state.name] =
131-
env.visiblePendingFields[state.name] || {}
132-
env.visiblePendingFields[state.name].initialValues = true
136+
env.hiddenPendingFields[state.name] =
137+
env.hiddenPendingFields[state.name] || {}
138+
env.hiddenPendingFields[state.name].initialValues = true
133139
}
134140
}
135141
})
@@ -227,6 +233,28 @@ export function createForm<FieldProps, VirtualFieldProps>(
227233
const errorsChanged = field.isDirty('errors')
228234
const userUpdateFieldPath =
229235
env.userUpdateFields[env.userUpdateFields.length - 1]
236+
237+
const syncField = () => {
238+
if (env.hiddenPendingFields[published.name]) {
239+
field.setState((state: IFieldState) => {
240+
if (env.hiddenPendingFields[state.name].values) {
241+
syncFieldValues(state)
242+
}
243+
if (env.hiddenPendingFields[state.name].initialValues) {
244+
syncFieldIntialValues(state)
245+
}
246+
delete env.hiddenPendingFields[state.name]
247+
})
248+
}
249+
}
250+
251+
const notifyFormValuesChange = () => {
252+
if (isFn(options.onChange)) {
253+
options.onChange(state.getSourceState(state => clone(state.values)))
254+
}
255+
heart.publish(LifeCycleTypes.ON_FORM_VALUES_CHANGE, state)
256+
}
257+
230258
if (initializedChanged) {
231259
heart.publish(LifeCycleTypes.ON_FIELD_INIT, field)
232260
const isEmptyValue = !isValid(published.value)
@@ -239,34 +267,34 @@ export function createForm<FieldProps, VirtualFieldProps>(
239267
})
240268
}
241269
}
270+
const wasHidden =
271+
published.visible == false || published.unmounted === true
242272
if (valueChanged) {
243-
userUpdating(field, () => {
244-
setFormValuesIn(path, published.value)
245-
})
273+
if (!wasHidden) {
274+
userUpdating(field, () => {
275+
setFormValuesIn(path, published.value)
276+
})
277+
}
246278
heart.publish(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, field)
247279
}
248280
if (initialValueChanged) {
249-
setFormInitialValuesIn(path, published.initialValue)
281+
if (!wasHidden) {
282+
setFormInitialValuesIn(path, published.initialValue)
283+
}
250284
heart.publish(LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE, field)
251285
}
252286
if (displayChanged || visibleChanged) {
253287
if (visibleChanged) {
254-
if (!published.visible) {
255-
deleteFormValuesIn(path, true)
256-
} else {
257-
setFormValuesIn(path, published.value)
258-
if (env.visiblePendingFields[published.name]) {
259-
field.setState((state: IFieldState) => {
260-
if (env.visiblePendingFields[state.name].values) {
261-
syncFieldValues(state)
262-
}
263-
if (env.visiblePendingFields[state.name].initialValues) {
264-
syncFieldIntialValues(state)
265-
}
266-
delete env.visiblePendingFields[state.name]
267-
})
288+
userUpdating(field, () => {
289+
if (!published.visible) {
290+
deleteFormValuesIn(path, true)
291+
//考虑到隐藏删值,不应该同步子树,但是需要触发表单变化事件
292+
notifyFormValuesChange()
293+
} else {
294+
setFormValuesIn(path, published.value)
295+
syncField()
268296
}
269-
}
297+
})
270298
}
271299
graph.eachChildren(path, childState => {
272300
childState.setState((state: IFieldState<FieldProps>) => {
@@ -286,8 +314,11 @@ export function createForm<FieldProps, VirtualFieldProps>(
286314
userUpdating(field, () => {
287315
if (published.unmounted) {
288316
deleteFormValuesIn(path, true)
317+
//考虑到隐藏删值,不应该同步子树,但是需要触发表单变化事件
318+
notifyFormValuesChange()
289319
} else {
290320
setFormValuesIn(path, published.value)
321+
syncField()
291322
}
292323
})
293324
heart.publish(LifeCycleTypes.ON_FIELD_UNMOUNT, field)
@@ -416,6 +447,7 @@ export function createForm<FieldProps, VirtualFieldProps>(
416447
visible,
417448
display,
418449
computeState,
450+
dataType,
419451
useDirty,
420452
props
421453
}: Exclude<IFieldStateProps, 'dataPath' | 'nodePath'>): IField {
@@ -430,6 +462,7 @@ export function createForm<FieldProps, VirtualFieldProps>(
430462
nodePath,
431463
dataPath,
432464
computeState,
465+
dataType,
433466
useDirty: isValid(useDirty) ? useDirty : options.useDirty
434467
})
435468
field.subscription = {
@@ -780,7 +813,10 @@ export function createForm<FieldProps, VirtualFieldProps>(
780813
return arr
781814
},
782815
validate(opts?: IFormExtendedValidateFieldOptions) {
783-
return validate(field.getSourceState(state => state.path), opts)
816+
return validate(
817+
field.getSourceState(state => state.path),
818+
opts
819+
)
784820
}
785821
}
786822
}
@@ -1041,9 +1077,8 @@ export function createForm<FieldProps, VirtualFieldProps>(
10411077

10421078
function userUpdating(field: IField | IVirtualField, fn?: () => void) {
10431079
if (!field) return
1044-
const nodePath = field.getSourceState(state => state.path)
1045-
if (nodePath)
1046-
env.userUpdateFields.push(field.getSourceState(state => state.path))
1080+
const nodePath = field.state.path
1081+
if (nodePath) env.userUpdateFields.push(nodePath)
10471082
if (isFn(fn)) {
10481083
fn()
10491084
}
@@ -1223,7 +1258,7 @@ export function createForm<FieldProps, VirtualFieldProps>(
12231258
userUpdateFields: [],
12241259
taskIndexes: {},
12251260
removeNodes: {},
1226-
visiblePendingFields: {},
1261+
hiddenPendingFields: {},
12271262
lastShownStates: {},
12281263
submittingTask: undefined
12291264
}

packages/core/src/shared/model.ts

+46-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
Subscribable,
88
FormPath,
99
FormPathPattern,
10-
isValid
10+
isValid,
11+
toArr
1112
} from '@uform/shared'
1213
import produce, { Draft, setAutoFreeze } from 'immer'
1314
import {
@@ -21,6 +22,18 @@ const hasProxy = !!globalThisPolyfill.Proxy
2122

2223
setAutoFreeze(false)
2324

25+
const defaults = (...args:any[]):any=>{
26+
const result = {}
27+
each(args,(target)=>{
28+
each(target,(value,key)=>{
29+
if(isValid(value)){
30+
result[key] = value
31+
}
32+
})
33+
})
34+
return result
35+
}
36+
2437
export const createStateModel = <State = {}, Props = {}>(
2538
Factory: IStateModelFactory<State, Props>
2639
): IStateModelProvider<State, Props> => {
@@ -32,6 +45,7 @@ export const createStateModel = <State = {}, Props = {}>(
3245
useDirty?: boolean
3346
computeState?: (draft: State, prevState: State) => void
3447
}
48+
public cacheProps?: any
3549
public displayName?: string
3650
public dirtyNum: number
3751
public dirtys: StateDirtyMap<State>
@@ -44,10 +58,7 @@ export const createStateModel = <State = {}, Props = {}>(
4458
super()
4559
this.state = { ...Factory.defaultState }
4660
this.prevState = { ...Factory.defaultState }
47-
this.props = {
48-
...Factory.defaultProps,
49-
...defaultProps
50-
}
61+
this.props = defaults(Factory.defaultProps,defaultProps)
5162
this.dirtys = {}
5263
this.dirtyNum = 0
5364
this.stackCount = 0
@@ -100,6 +111,36 @@ export const createStateModel = <State = {}, Props = {}>(
100111
}
101112
}
102113

114+
watchProps = <T extends { [key: string]: any }>(
115+
props: T,
116+
keys: string[],
117+
callback: (
118+
changedProps: {
119+
[key: string]: any
120+
},
121+
props?: T
122+
) => void
123+
) => {
124+
if (!this.cacheProps) {
125+
this.cacheProps = { ...props }
126+
} else {
127+
let changeNum = 0
128+
let changedProps = {}
129+
toArr(keys).forEach((key: string) => {
130+
if (!isEqual(this.cacheProps[key], props[key])) {
131+
changeNum++
132+
changedProps[key] = props[key]
133+
}
134+
})
135+
if (changeNum > 0) {
136+
if (isFn(callback)) {
137+
callback(changedProps, props)
138+
}
139+
this.cacheProps = { ...props }
140+
}
141+
}
142+
}
143+
103144
setState = (
104145
callback: (state: State | Draft<State>) => State | void,
105146
silent = false

0 commit comments

Comments
 (0)