Skip to content

Commit

Permalink
Merge pull request #163 from janryWang/fix_sync_value
Browse files Browse the repository at this point in the history
fix(@uform/core and @uform/react): fix sync value
  • Loading branch information
janryWang authored Jul 11, 2019
2 parents 97caa9c + bc466e9 commit d41de67
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 26 deletions.
18 changes: 8 additions & 10 deletions packages/core/src/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export class Field implements IField {

public hiddenFromParent: boolean

public shownFromParent: boolean

public initialValue: any

public namePath: string[]
Expand All @@ -71,8 +73,6 @@ export class Field implements IField {

private destructed: boolean

private initialized: boolean

private alreadyHiddenBeforeUnmount: boolean

private fieldbrd: Broadcast<any, any, any>
Expand All @@ -94,9 +94,7 @@ export class Field implements IField {
this.errors = []
this.props = {}
this.effectErrors = []
this.initialized = false
this.initialize(options)
this.initialized = true
}

public initialize(options: IFieldOptions) {
Expand Down Expand Up @@ -128,12 +126,12 @@ export class Field implements IField {
this.editable = !isEmpty(editable) ? editable : this.getContextEditable()
}

if (!this.initialized) {
if (isEmpty(this.value) && !isEmpty(this.initialValue)) {
this.value = clone(this.initialValue)
this.context.setIn(this.name, this.value)
this.context.setInitialValueIn(this.name, this.initialValue)
}
if (
!isEmpty(this.initialValue) &&
(isEmpty(this.value) || (this.removed && !this.shownFromParent))
) {
this.value = clone(this.initialValue)
this.context.setIn(this.name, this.value)
}

this.mount()
Expand Down
28 changes: 21 additions & 7 deletions packages/core/src/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Editable = boolean | ((name: string) => boolean)
const defaults = <T>(opts: T): T =>
({
initialValues: {},
values: {},
onSubmit: (values: any) => {},
effects: ($: any) => {},
...opts
Expand Down Expand Up @@ -92,7 +93,10 @@ export class Form {
this.updateBuffer = new BufferList()
this.editable = opts.editable
this.schema = opts.schema || {}
this.initialize(this.options.initialValues)
this.initialize({
values: this.options.values,
initialValues: this.options.initialValues
})
this.initializeEffects()
this.initialized = true
this.destructed = false
Expand Down Expand Up @@ -188,7 +192,7 @@ export class Form {
field.initialize({
path: options.path,
onChange: options.onChange,
value: !isEmpty(value) ? value : initialValue,
value,
initialValue
} as IFieldOptions)
this.asyncUpdate(() => {
Expand All @@ -197,7 +201,7 @@ export class Form {
} else {
this.fields[name] = new Field(this, {
name,
value: !isEmpty(value) ? value : initialValue,
value,
path: options.path,
initialValue,
props: options.props
Expand Down Expand Up @@ -366,11 +370,13 @@ export class Form {
if (field.hiddenFromParent) {
field.visible = visible
field.hiddenFromParent = false
field.shownFromParent = true
field.dirty = true
}
} else {
field.visible = visible
field.hiddenFromParent = true
field.shownFromParent = false
field.dirty = true
}
}
Expand Down Expand Up @@ -526,18 +532,26 @@ export class Form {
}
}

public initialize(values = this.state.initialValues) {
public initialize({
initialValues = this.state.initialValues,
values = this.state.values
}) {
const lastValues = this.state.values
const lastDirty = this.state.dirty
const currentInitialValues = clone(initialValues) || {}
const currentValues = isEmpty(values)
? clone(currentInitialValues)
: clone(values) || {}
this.state = {
valid: true,
invalid: false,
errors: [],
pristine: true,
initialValues: clone(values) || {},
values: clone(values) || {},
initialValues: currentInitialValues,
values: currentValues,
dirty:
lastDirty || (this.initialized ? !isEqual(values, lastValues) : false)
lastDirty ||
(this.initialized ? !isEqual(currentValues, lastValues) : false)
}
if (this.options.onFormChange && !this.initialized) {
this.subscribe(this.options.onFormChange)
Expand Down
19 changes: 14 additions & 5 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { IFormOptions } from '@uform/types'
import { IFormOptions, ISchema } from '@uform/types'
import {
setLocale as setValidationLocale,
setLanguage as setValidationLanguage
} from '@uform/validator'

import { Form } from './form'
import { caculateSchemaInitialValues, isFn, each } from './utils'
import { caculateSchemaInitialValues, isFn, each, isEmpty } from './utils'

export * from './path'

export const createForm = ({
initialValues,
values,
onSubmit,
onReset,
schema,
Expand All @@ -23,15 +24,23 @@ export const createForm = ({
onValidateFailed
}: IFormOptions) => {
let fields = []
initialValues = caculateSchemaInitialValues(
let calculatedValues = caculateSchemaInitialValues(
schema,
initialValues,
({ name, path, schemaPath }, schema, value: any) => {
isEmpty(values) ? initialValues : values,
({ name, path, schemaPath }, schema: ISchema, value: any) => {
fields.push({ name, path, schemaPath, schema, value })
}
)

if (isEmpty(values)) {
initialValues = calculatedValues
} else {
values = calculatedValues
}

const form = new Form({
initialValues,
values,
onSubmit,
onReset,
subscribes,
Expand Down
106 changes: 106 additions & 0 deletions packages/react/src/__tests__/dynamic.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -575,3 +575,109 @@ test('dynamic change functions onChange/onReset/onSubmit/onValidateFailed', asyn
// onSubmit
expect(queryAllByText('valueD-456').length).toBe(1)
})

test('dynamic remove field and relationship needs to be retained', async () => {
const TestComponent = () => {
return (
<SchemaForm
initialValues={{
container: [{ bb: '123' }, { bb: '123' }]
}}
effects={($, { setFieldState }) => {
$('onFieldChange', 'container.*.bb').subscribe(({ value, name }) => {
const siblingName = FormPath.transform(name, /\d+/, $d => {
return `container.${$d}.aa`
})
setFieldState(FormPath.match(siblingName), state => {
state.visible = value !== '123'
})
})
}}
>
<Field name="container" type="array" x-component="container">
<Field name="object" type="object">
<FormCard>
<Field name="aa" required type="string" />
<Field name="bb" required type="string" />
</FormCard>
</Field>
</Field>
<button type="submit">Submit</button>
</SchemaForm>
)
}

const { queryAllByTestId, queryByText, queryAllByText } = render(
<TestComponent />
)
expect(queryAllByTestId('input').length).toBe(2)
let removes
await sleep(33)
removes = queryAllByText('Remove Field')
fireEvent.click(removes[removes.length - 1])
await sleep(33)
removes = queryAllByText('Remove Field')
fireEvent.click(removes[removes.length - 1])
await sleep(33)
expect(queryAllByTestId('input').length).toBe(0)
await sleep(33)
fireEvent.click(queryByText('Add Field'))
await sleep(33)
fireEvent.click(queryByText('Add Field'))
await sleep(33)
expect(queryAllByTestId('input').length).toBe(2)
expect(queryAllByTestId('input')[0].value).toBe('123')
expect(queryAllByTestId('input')[1].value).toBe('123')
})

test('after deleting a component should not be sync an default value', async () => {
const TestComponent = () => {
return (
<SchemaForm
value={{
container: [{ bb: '123' }, { bb: '123' }]
}}
effects={($, { setFieldState }) => {
$('onFieldChange', 'container.*.bb').subscribe(({ value, name }) => {
const siblingName = FormPath.transform(name, /\d+/, $d => {
return `container.${$d}.aa`
})
setFieldState(FormPath.match(siblingName), state => {
state.visible = value === '123'
})
})
}}
>
<Field name="container" type="array" x-component="container">
<Field name="object" type="object">
<Field name="aa" required type="string" />
<Field name="bb" required type="string" />
</Field>
</Field>
<button type="submit">Submit</button>
</SchemaForm>
)
}

const { queryAllByTestId, queryByText, queryAllByText } = render(
<TestComponent />
)
expect(queryAllByTestId('input').length).toBe(4)
let removes
await sleep(33)
removes = queryAllByText('Remove Field')
fireEvent.click(removes[removes.length - 1])
await sleep(33)
removes = queryAllByText('Remove Field')
fireEvent.click(removes[removes.length - 1])
await sleep(33)
expect(queryAllByTestId('input').length).toBe(0)
await sleep(33)
fireEvent.click(queryByText('Add Field'))
await sleep(33)
fireEvent.click(queryByText('Add Field'))
await sleep(33)
expect(queryAllByTestId('input').length).toBe(2)
expect(queryAllByTestId('input')[0].value).toBe('')
expect(queryAllByTestId('input')[1].value).toBe('')
})
2 changes: 1 addition & 1 deletion packages/react/src/decorators/markup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const SchemaMarkup = createHOC((options, SchemaForm) => {
)}
<SchemaForm
{...others}
defaultValue={value || defaultValue}
defaultValue={defaultValue}
value={value}
initialValues={initialValues}
schema={finalSchema}
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/state/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const StateForm = createHOC((options, Form) => {
this.initialized = false
this.form = createForm({
initialValues: props.defaultValue || props.initialValues,
values: props.value,
effects: props.effects,
subscribes: props.subscribes,
schema: props.schema,
Expand Down Expand Up @@ -197,7 +198,7 @@ export const StateForm = createHOC((options, Form) => {
!isEmpty(initialValues) &&
!isEqual(initialValues, prevProps.initialValues)
) {
this.form.initialize(initialValues)
this.form.initialize({ initialValues })
}
if (!isEmpty(editable) && !isEqual(editable, prevProps.editable)) {
this.form.changeEditable(editable)
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface IField {
invalid: boolean
visible: boolean
hiddenFromParent: boolean
shownFromParent: boolean
required: boolean
editable: boolean
loading: boolean
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface IFormOptions {
editable: boolean | ((nam: string) => boolean)
effects: IEffects
defaultValue?: object
values?: object
initialValues?: object
schema: ISchema | {}
subscribes: ISubscribers
Expand Down
41 changes: 41 additions & 0 deletions packages/utils/src/__tests__/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,47 @@ test('test accessor with large path', () => {
expect(isEqual(getIn(value, 'array.0.[aa,bb]'), [123, 321])).toBeTruthy()
})

test('test setIn auto create array', () => {
const value = {}
setIn(value, 'array.0.bb.2', 'hello world')
expect(
isEqual(value, {
array: [
{
bb: [undefined, undefined, 'hello world']
}
]
})
).toBeTruthy()
})

test('test setIn dose not affect other items', () => {
const value = {
aa: [
{
dd: [
{
ee: '是'
}
],
cc: '1111'
}
]
}

setIn(value, 'aa.1.dd.0.ee', '否')
expect(
isEqual(value.aa[0], {
dd: [
{
ee: '是'
}
],
cc: '1111'
})
).toBeTruthy()
})

test('destruct getIn', () => {
// getIn 通过解构表达式从扁平数据转为复合嵌套数据
const value = { a: { b: { c: 2, d: 333 } } }
Expand Down
3 changes: 1 addition & 2 deletions packages/utils/src/accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,12 +477,11 @@ function _setIn(obj: any, path: Path, value: any) {

for (let i = 0; i < pathArr.length; i++) {
const p = pathArr[i]

if (!isObj(obj[p])) {
if (obj[p] === undefined && value === undefined) {
return
}
obj[p] = {}
obj[p] = /^\d+$/.test(pathArr[i + 1 + '']) ? [] : {}
}

if (i === pathArr.length - 1) {
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const NATIVE_KEYS = [
['WeakMap', (map: any) => new WeakMap(map)],
['WeakSet', (set: any) => new WeakSet(set)],
['Set', (set: any) => new Set(set)],
['Date', (date: any) => new Date(date)],
'FileList',
'File',
'URL',
Expand Down

0 comments on commit d41de67

Please sign in to comment.