@formilyv1/react
v1.3.17
Published
English | [简体中文](./README.zh-cn.md)
Downloads
4
Readme
@formilyv1/react
English | 简体中文
@formilyv1/react is based on
react
and @formilyv1/core is already built in. It provide API to manuplate form state and components for rendering support. it mainly includes:
- Form
- Field
- VirtualField
- FormaSpy
- FormProvider
- FormConsumer(deprecated,pls using FormSpy)
- createFormActions (create sync API to manuplate form state)
- createAsyncFormActions (create async API to manuplate form state)
- FormEffectHooks (LifeCycles Hook)
Install
npm install --save @formilyv1/react
Table Of Contents
Usage
Quick Start
import React from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
FormPath,
createFormActions,
FormSpy,
FormProvider,
FormConsumer,
FormEffectHooks
} from '@formilyv1/react'
const { onFormInit$, onFormInputChange$, onFieldInputChange$ } = FormEffectHooks
const actions = createFormActions()
const App = () => {
return (
<Form
actions={actions}
effects={() => {
onFormInit$().subscribe(() => {
console.log('initialized')
})
onFieldInputChange$().subscribe(state => {
console.log('field change', state)
})
}}
onChange={() => {}}
>
<React.Fragment>
<label>username: </label>
<Field name="username">
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
{state.errors}
{state.warnings}
</React.Fragment>
)}
</Field>
</React.Fragment>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Basic Field
Example:Show you how to bind the <input>
field and subsequent examples are based on this field
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<div>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
{state.errors}
{state.warnings}
</div>
)}
</Field>
)
Validation
Example:required validation + error type validation + warning type validation + custom validation The type of rules is ValidatePatternRules which is InternalFormats | CustomValidator | ValidateDescription | ValidateArrayRules
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions } from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<h5>required validation</h5>
<span>username</span>
<InputField name="username" required />
<h5>error type validation</h5>
<span>age</span>
<InputField
name="age"
rules={[
val =>
!val ? { type: 'error', message: 'age is required' } : undefined
]}
/>
<h5>warning type validation</h5>
<span>gender</span>
<InputField
name="gender"
rules={[
val =>
!val
? { type: 'warning', message: 'gender is required' }
: undefined
]}
/>
<h5>built-in validation default to error type validation</h5>
<span>id</span>
<InputField
name="id"
rules={[
{
format: 'number',
message: 'id is not a number.'
}
]}
/>
<h5>custom validation</h5>
<span>verifyCode</span>
<InputField
name="verifyCode"
rules={[
{
validator(value) {
return !value
? 'This field can not be empty, please enter {{scope.outerVariable}}'
: undefined
},
scope: {
outerVariable: '456'
}
},
{
validator(value) {
return value === '456'
? { type: 'error', message: 'This field can not be 456' }
: undefined
}
}
]}
/>
<div>
<button
onClick={() => {
const result = actions.validate()
console.log(actions.getFormState(state => state.values))
result.then(validateResp => {
console.log(validateResp)
})
}}
>
validate
</button>
</div>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Object Field
Example:User info user(username, age)
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions } from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<span>user</span>
<Field
name="user"
initialValue={{
username: undefined,
age: undefined
}}
>
{({ state, mutators }) => {
return (
<React.Fragment>
{Object.keys(state.value).map(key => {
if (!mutators.exist(key)) return
return (
<div key={key}>
<span>{key}</span>
<InputField name={`user.${key}`} />
<button
onClick={() => {
mutators.remove(key)
}}
>
x
</button>
</div>
)
})}
<button
onClick={() => {
mutators.change({
...state.value,
[new Date().getTime()]: new Date().getTime()
})
}}
>
+
</button>
<button
onClick={() => {
console.log(
'values',
actions.getFormState(state => state.values)
)
}}
>
print
</button>
</React.Fragment>
)
}}
</Field>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
ArrayField
Example:Id list
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions } from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<Field name="idList" initialValue={['1', '2', '3']}>
{({ state, mutators }) => {
return (
<React.Fragment>
{state.value.map((item, index) => {
return (
<div key={index}>
<InputField name={`idList[${index}]`} />
<button onClick={() => mutators.remove(index)}>
Remove
</button>
</div>
)
})}
<button onClick={() => mutators.push()}>Add Item</button>
</React.Fragment>
)
}}
</Field>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
ArrayField<Object>
Example:User list
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions } from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<Field
name="userList"
initialValue={[
{ username: 'bobby', age: 21 },
{ username: 'lily', age: 20 }
]}
>
{({ state, mutators }) => {
return (
<React.Fragment>
{state.value.map((item, index) => {
return (
<div key={index}>
<Field name={`userList[${index}]`} initialValue={{}}>
{({ state: innerState, mutators: innerMutator }) => {
return (
<React.Fragment>
{Object.keys(innerState.value).map(key => {
if (!innerMutator.exist(key)) return
return (
<React.Fragment key={key}>
<InputField
name={`userList[${index}].${key}`}
/>
<button
onClick={() => {
innerMutator.remove(key)
}}
>
x
</button>
</React.Fragment>
)
})}
<button
onClick={() => {
innerMutator.change({
...innerState.value,
[new Date().getTime()]: new Date().getTime()
})
}}
>
+
</button>
</React.Fragment>
)
}}
</Field>
<button onClick={() => mutators.remove(index)}>
Remove
</button>
</div>
)
})}
<button
onClick={() =>
mutators.push({
username: undefined,
age: undefined
})
}
>
Add Item
</button>
<button
onClick={() =>
console.log(actions.getFormState(state => state.values))
}
>
print
</button>
</React.Fragment>
)
}}
</Field>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
display visible
Example: see how display
与 visible
affect values
import React from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
createFormActions,
LifeCycleTypes,
FormSpy
} from '@formilyv1/react'
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const CheckedField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
type="checkbox"
onChange={() => {
mutators.change(!state.value)
}}
checked={!!state.value}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const actions = createFormActions()
const App = () => {
return (
<Form
actions={actions}
effects={($, { validate, setFieldState }) => {
$(LifeCycleTypes.ON_FORM_INIT).subscribe(() => {
setFieldState('displayTrigger', state => (state.value = true))
setFieldState('visibleTrigger', state => (state.value = true))
setFieldState('a', state => (state.value = 1))
setFieldState('b', state => (state.value = 2))
})
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'visibleTrigger').subscribe(
fieldState => {
setFieldState('a', state => {
state.visible = fieldState.value
})
}
)
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'displayTrigger').subscribe(
fieldState => {
setFieldState('b', state => {
state.display = fieldState.value
})
}
)
}}
>
<div>
<CheckedField label="visible" name="visibleTrigger" />
<InputField name="a" label="a" />
</div>
<div>
<CheckedField label="display" name="displayTrigger" />
<InputField name="b" label="b" />
</div>
<FormSpy>
{({ state, form }) => {
return (
<div>
{JSON.stringify(form.getFormState(state => state.values))}
</div>
)
}}
</FormSpy>
<button
onClick={() => console.log(actions.getFormState(state => state.values))}
>
print
</button>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Linkage
Example:Show/hide field and modified props/value by using effects
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions, LifeCycleTypes } from '@formilyv1/react'
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const CheckedField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
type="checkbox"
onChange={() => {
mutators.change(!state.value)
}}
checked={!!state.value}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const actions = createFormActions()
const App = () => {
return (
<Form
actions={actions}
effects={($, { setFieldState }) => {
$(LifeCycleTypes.ON_FORM_INIT).subscribe(() => {
setFieldState('a~', state => (state.visible = false))
})
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'trigger').subscribe(
triggerState => {
setFieldState('a~', state => {
state.visible = triggerState.value
})
}
)
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'a').subscribe(fieldState => {
setFieldState('a-copy', state => {
state.value = fieldState.value
})
})
}}
>
<CheckedField name="trigger" label="show/hide" />
<div>
<InputField label="a" name="a" />
</div>
<div>
<InputField label="a-copy" name="a-copy" />
</div>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Async Linkage
Example:Change dataSource in select asynchronously by effects
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions, LifeCycleTypes } from '@formilyv1/react'
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const CheckedField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
type="checkbox"
onChange={() => {
mutators.change(!state.value)
}}
checked={!!state.value}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const SelectField = props => (
<Field {...props}>
{({ state, mutators }) => {
const { loading, dataSource = [] } = state.props
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<select
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
>
{dataSource.map(item => (
<option value={item.value}>{item.label}</option>
))}
</select>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const actions = createFormActions()
const App = () => {
return (
<Form
actions={actions}
effects={($, { setFieldState }) => {
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'trigger').subscribe(
fieldState => {
const dataSource = [
{ label: 'aa', value: 'aa' },
{ label: 'bb', value: 'bb' }
]
setFieldState('sync-source', state => {
state.props.dataSource = fieldState.value ? dataSource : []
})
setFieldState('async-source', state => {
state.props.loading = true
})
setTimeout(() => {
setFieldState('async-source', state => {
state.props.loading = false
state.props.dataSource = fieldState.value ? dataSource : []
})
}, 300)
}
)
}}
>
<CheckedField name="trigger" label="show/reset dataSource" />
<div>
<SelectField label="sync-source" name="sync-source" />
</div>
<div>
<SelectField label="async-source" name="async-source" />
</div>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Linkage Validation
Example:validation when form mounted and re-trigger validation when field change
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions, LifeCycleTypes } from '@formilyv1/react'
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const actions = createFormActions()
const App = () => {
return (
<Form
actions={actions}
effects={($, { validate, setFieldState }) => {
$(LifeCycleTypes.ON_FORM_MOUNT).subscribe(() => {
validate()
})
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'a').subscribe(fieldState => {
setFieldState('a-copy', state => {
state.value = fieldState.value
})
})
}}
>
<InputField label="a" name="a" />
<div>
<InputField label="a-copy" name="a-copy" required />
</div>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Complex Linkage
Example:See how ArrayField communicate with other field by using effects
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions, LifeCycleTypes } from '@formilyv1/react'
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const actions = createFormActions()
const App = () => {
return (
<Form
actions={actions}
effects={($, { validate, setFieldState }) => {
$(LifeCycleTypes.ON_FORM_INIT).subscribe(() => {
setFieldState('userList.*.username', state => {
state.visible = false
})
})
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'trigger').subscribe(
fieldState => {
setFieldState('userList.*.username', state => {
state.visible = fieldState.value
})
}
)
}}
>
<div>
<Field name="trigger" label="show/hide username">
{({ state, mutators }) => {
return (
<input
type="checkbox"
onChange={mutators.change}
checked={state.value ? 'checked' : undefined}
/>
)
}}
</Field>
</div>
<div>
<Field
initialValue={[
{ username: 'bobby', age: 22 },
{ username: 'lily', age: 21 }
]}
name="userList"
>
{({ state, mutators }) => {
return (
<React.Fragment>
{state.value.map((item, index) => {
return (
<div key={index}>
<Field name={`userList[${index}]`} initialValue={{}}>
{({ state: innerState, mutators: innerMutator }) => {
return (
<React.Fragment>
{Object.keys(innerState.value).map(key => {
if (!innerMutator.exist(key)) return
return (
<React.Fragment key={key}>
<InputField
label={key}
name={`userList[${index}].${key}`}
/>
</React.Fragment>
)
})}
<button
onClick={() => {
innerMutator.change({
...innerState.value,
[new Date().getTime()]: new Date().getTime()
})
}}
>
+
</button>
</React.Fragment>
)
}}
</Field>
<button onClick={() => mutators.remove(index)}>
Remove
</button>
</div>
)
})}
<button
onClick={() =>
mutators.push({
username: undefined,
age: undefined
})
}
>
Add Item
</button>
<button
onClick={() =>
console.log(actions.getFormState(state => state.values))
}
>
print
</button>
</React.Fragment>
)
}}
</Field>
</div>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Reuse Effects
Make your own reusable effects.
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions, FormEffectHooks } from '@formilyv1/react'
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const CheckedField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
type="checkbox"
onChange={() => {
mutators.change(!state.value)
}}
checked={!!state.value}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const { onFormMount$, onFieldValueChange$ } = FormEffectHooks
const getEffects = () => {
const actions = createFormActions()
onFormMount$().subscribe(() => {
actions.setFieldState('a~', state => (state.visible = false))
})
onFieldValueChange$('trigger').subscribe(triggerState => {
actions.setFieldState('a~', state => {
state.visible = triggerState.value
})
})
onFieldValueChange$('a').subscribe(fieldState => {
actions.setFieldState('a-copy', state => {
state.value = fieldState.value
})
})
}
const actions = createFormActions()
const App = () => {
return (
<Form
actions={actions}
effects={() => {
getEffects()
}}
>
<CheckedField name="trigger" label="show/hide" />
<div>
<InputField label="a" name="a" />
</div>
<div>
<InputField label="a-copy" name="a-copy" />
</div>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Combo
Example:Combo value of username and age. Check FormSpy for more inforation.
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions, FormSpy } from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<label>username</label>
<InputField name="username" />
<label>age</label>
<InputField name="age" />
<FormSpy>
{({ state, form }) => {
return (
<div>
name: {form.getFieldValue('username')}
<br />
age: {form.getFieldValue('age')}
</div>
)
}}
</FormSpy>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Provide and FormSpy
Dictionary
--app
|---components
|---customForm
Example:Cross-file consumption form state, Check FormProvider and FormSpy for more information.
import React from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
createFormActions,
FormSpy,
FormProvider
} from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const CustomForm = () => {
return (
<Form actions={actions}>
<label>username</label>
<InputField name="username" />
<label>age</label>
<InputField name="age" />
</Form>
)
}
const App = () => {
return (
<FormProvider>
<CustomForm />
<FormSpy>
{({ state, form }) => {
return (
<div>
name: {form.getFieldValue('username')}
<br />
age: {form.getFieldValue('age')}
</div>
)
}}
</FormSpy>
</FormProvider>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Deconstruction
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
createFormActions,
FormSpy,
FormPath
} from '@formilyv1/react'
const actions = createFormActions()
const App = () => {
return (
<Form actions={actions}>
<label>range input</label>
<Field name="[start,end]">
{({ state, mutators }) => {
const [start, end] = state.value
return (
<div>
<label>start</label>
<input
value={start}
onChange={e => {
mutators.change([e.target.value, end])
}}
/>
<label>end</label>
<input
value={end}
onChange={e => {
mutators.change([start, e.target.value])
}}
/>
</div>
)
}}
</Field>
<button
onClick={() => {
actions.setFormState(state => {
state.values = { start: 'x', end: 'y' }
})
}}
>
set value
</button>
<FormSpy>
{({ state, form }) => {
return (
<div>
Form values:
<code>
<pre>
{JSON.stringify(
form.getFormState(state => state.values),
null,
2
)}
</pre>
</code>
</div>
)
}}
</FormSpy>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Complex Deconstruction
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
createFormActions,
FormSpy,
FormPath
} from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<Field name="{aa:{bb:{cc:destructor1,dd:[destructor2,destructor3],ee}}}">
{({ state, mutators }) => {
return (
<div>
<button
onClick={() => {
mutators.change({
aa: {
bb: {
cc: 123,
dd: [333, 444],
ee: 'abcde'
}
}
})
}}
>
set value
</button>
<div>Field value:</div>
<code>
<pre>{JSON.stringify(state.value, null, 2)}</pre>
</code>
</div>
)
}}
</Field>
<button
onClick={() => {
actions.setFieldState(
FormPath.match(
'[[{aa:{bb:{cc:destructor1,dd:\\[destructor2,destructor3\\],ee}}}]]'
),
state => {
state.value = {
aa: {
bb: {
cc: 'a',
dd: ['b', 'c'],
ee: 'd'
}
}
}
}
)
}}
>
outside set
</button>
<FormSpy>
{({ state, form }) => {
return (
<div>
Form values:
<code>
<pre>
{JSON.stringify(
form.getFormState(state => state.values),
null,
2
)}
</pre>
</code>
</div>
)
}}
</FormSpy>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Components
<Form/>
<Form>
Props
interface IFormProps {
// Form value
value?: any
defaultValue?: any // Form initial value
initialValues?: any
// formAPI
actions?: IFormActions | IFormAsyncActions
// effect
effects?: IFormEffect<any, IFormActions | IFormAsyncActions>
// IForm instance
form?: IForm // Form change event callback
onChange?: (values: Value) => void // Form submission event callback
onSubmit?: (values: Value) => void | Promise<Value> // Form reset event callback
onReset?: () => void // Form verification failure event callback
onValidateFailed?: (valideted: IFormValidateResult) => void
children?: React.ReactElement | ((form: IForm) => React.ReactElement)
// Whether to use the dirty check, the default will go immer accurate update
useDirty?: boolean
// Is it editable, overall control in the Form dimension
editable?: boolean
// Whether to go pessimistic check, stop the subsequent check when the first check fails
validateFirst?: boolean
}
<Field/>
<Field>
Props
interface IFieldStateUIProps {
// Node path
path?: FormPathPattern // Node path
nodePath?: FormPathPattern // Data path
dataPath?: FormPathPattern // Data path
name?: string // Field value, is equal to values[0]
value?: any // Field multi-parameter value, such as when the field onChange trigger, the event callback passed multi-parameter data, then the value of all parameters will be stored here
values?: any[] // Initial value
initialValue?: any // field extension properties
visible?: boolean //Field initial visible status(Whether the data and style is visible)
display?: boolean //Field initial display status(Whether the style is visible)
props?: FieldProps // Check the rules, the specific type description refers to the following documents
rules?: ValidatePatternRules[] // Is it required?
required?: boolean // Is it editable?
editable?: boolean // Whether to use the dirty check, the default will go immer accurate update
useDirty?: boolean
// Field state calculation container, mainly used to extend the core linkage rules
computeState?: (draft: IFieldState, prevState: IFieldState) => void
// type of trigger validation
triggerType?: 'onChange' | 'onBlur'
// get value from browser event(eg. e.target.value)
getValueFromEvent?: (...args: any[]) => any
children?: React.ReactElement | ((api: IFieldAPI) => React.ReactElement)
}
Usage
Example:All type of field
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions } from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<div>
<h5>Basic Field</h5>
<Field name="id">
{({ state, mutator }) => {
return <input value={state.value} onChange={mutator} />
}}
</Field>
</div>
<div>
<h5>Object Field</h5>
<Field
name="user"
initialValue={{
username: undefined,
age: undefined
}}
>
{({ state, mutators }) => {
return (
<React.Fragment>
{Object.keys(state.value).map(key => {
if (!mutators.exist(key)) return
return (
<div key={key}>
<span>{key}</span>
<InputField name={`user.${key}`} />
<button
onClick={() => {
mutators.remove(key)
}}
>
x
</button>
</div>
)
})}
<button
onClick={() => {
mutators.change({
...state.value,
[new Date().getTime()]: new Date().getTime()
})
}}
>
+
</button>
</React.Fragment>
)
}}
</Field>
</div>
<div>
<h5>ArrayField Field</h5>
<Field name="idList" initialValue={['1', '2', '3']}>
{({ state, mutators }) => {
return (
<React.Fragment>
{state.value.map((item, index) => {
return (
<div key={index}>
<InputField name={`idList[${index}]`} />
<button onClick={() => mutators.remove(index)}>
Remove
</button>
</div>
)
})}
<button onClick={() => mutators.push()}>Add Item</button>
</React.Fragment>
)
}}
</Field>
</div>
<div>
<h5>ArrayObject Field</h5>
<Field
name="userList"
initialValue={[
{ username: 'bobby', age: 21 },
{ username: 'lily', age: 20 }
]}
>
{({ state, mutators }) => {
return (
<React.Fragment>
{state.value.map((item, index) => {
return (
<div key={index}>
<Field name={`userList[${index}]`} initialValue={{}}>
{({ state: innerState, mutators: innerMutator }) => {
return (
<React.Fragment>
{Object.keys(innerState.value).map(key => {
if (!innerMutator.exist(key)) return
return (
<React.Fragment key={key}>
<InputField
name={`userList[${index}].${key}`}
/>
<button
onClick={() => {
innerMutator.remove(key)
}}
>
x
</button>
</React.Fragment>
)
})}
<button
onClick={() => {
innerMutator.change({
...innerState.value,
[new Date().getTime()]: new Date().getTime()
})
}}
>
+
</button>
</React.Fragment>
)
}}
</Field>
<button onClick={() => mutators.remove(index)}>
Remove
</button>
</div>
)
})}
<button
onClick={() =>
mutators.push({
username: undefined,
age: undefined
})
}
>
Add Item
</button>
</React.Fragment>
)
}}
</Field>
</div>
<button
onClick={() => console.log(actions.getFormState(state => state.values))}
>
print
</button>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
<VirtualField/>
<VirtualField>
Props
interface IVirtualFieldProps {
// Node path
path?: FormPathPattern // Node path
nodePath?: FormPathPattern // Data path
dataPath?: FormPathPattern // Data path
visible?: boolean //Field initial visible status(Whether the data and style is visible)
display?: boolean //Field initial display status(Whether the style is visible)
name?: string // Form extension properties
props?: FieldProps // Whether to use the dirty check, the default will go immer accurate update
useDirty?: boolean
// Field state calculation container, mainly used to extend the core linkage rules
computeState?: (draft: IFieldState, prevState: IFieldState) => void
children?: React.ReactElement | ((api: IFieldAPI) => React.ReactElement)
}
Usage
Example:Setting <Layout>
size from 100x100 to 200x200
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions, VirtualField } from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const Layout = ({ children, width = '100px', height = '100px' }) => {
return (
<div style={{ border: '1px solid #999', width, height }}>{children}</div>
)
}
const App = () => {
return (
<Form actions={actions}>
<Field name="user" initialValue={{}}>
{({ state, mutator }) => {
return (
<VirtualField name="user.layout">
{({ state: layoutState }) => {
return (
<Layout
width={layoutState.props.width}
height={layoutState.props.height}
>
<label>username</label>
<InputField name="username" />
<label>age</label>
<InputField name="age" />
</Layout>
)
}}
</VirtualField>
)
}}
</Field>
<button
onClick={() => {
// some where dynamic change layout's props
actions.setFieldState('user.layout', state => {
state.props.width = '200px'
state.props.height = '200px'
})
}}
>
change layout
</button>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
<FormSpy/>
<FormSpy>
Props
interface IFormSpyProps {
// selector, eg: [ LifeCycleTypes.ON_FORM_SUBMIT_START, LifeCycleTypes.ON_FORM_SUBMIT_END ]
selector?: string[] | string
// reducer
reducer?: (
state: any,
action: { type: string; payload: any },
form: IForm
) => any
children?: React.ReactElement | ((api: IFormSpyAPI) => React.ReactElement)
}
Usage
Example1: Form state change counter
import React from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
createFormActions,
FormSpy,
LifeCycleTypes
} from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<label>username</label>
<InputField name="username" />
<label>age</label>
<InputField name="age" />
<FormSpy
selector={LifeCycleTypes.ON_FORM_VALUES_CHANGE}
reducer={(state, action, form) => ({
count: state.count ? state.count + 1 : 1
})}
>
{({ state, type, form }) => {
return <div>count: {state.count || 0}</div>
}}
</FormSpy>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Example2:Combo
import React from 'react'
import ReactDOM from 'react-dom'
import { Form, Field, createFormActions, FormSpy } from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const App = () => {
return (
<Form actions={actions}>
<label>username</label>
<InputField name="username" />
<label>age</label>
<InputField name="age" />
<FormSpy>
{({ state, form }) => {
return (
<div>
name: {form.getFieldValue('username')}
<br />
age: {form.getFieldValue('age')}
</div>
)
}}
</FormSpy>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
<FormProvider/>
Used with FormSpy, often used in Cross-file consumption form state
Usage
import React from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
createFormActions,
FormSpy,
FormProvider
} from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => (
<React.Fragment>
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)}
</Field>
)
const CustomForm = () => {
return (
<Form actions={actions}>
<label>username</label>
<InputField name="username" />
<label>age</label>
<InputField name="age" />
</Form>
)
}
const App = () => {
return (
<FormProvider>
<CustomForm />
<FormSpy>
{({ state, form }) => {
return (
<div>
name: {form.getFieldValue('username')}
<br />
age: {form.getFieldValue('age')}
</div>
)
}}
</FormSpy>
</FormProvider>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
<FormConsumer/>(deprecated,pls using <FormSpy/>)
<FormConsumer>
Props
interface IFormConsumerProps {
// eg.[ LifeCycleTypes.ON_FORM_SUBMIT_START, LifeCycleTypes.ON_FORM_SUBMIT_END ]
selector?: string[] | string
children?:
| React.ReactElement
| ((api: IFormConsumerAPI) => React.ReactElement)
}
Hook
useFormEffects
Implement local effects by using useFormEffects. Same effect as the example of Linkage Note: The life cycle of the listener starts from
ON_FORM_MOUNT
Signature
(effects: IFormEffect): void
import React from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
createFormActions,
useFormEffects,
LifeCycleTypes
} from '@formilyv1/react'
const actions = createFormActions()
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const CheckedField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
type="checkbox"
onChange={() => {
mutators.change(!state.value)
}}
checked={!!state.value}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const FormFragment = () => {
useFormEffects(($, { setFieldState }) => {
$(LifeCycleTypes.ON_FORM_INIT).subscribe(() => {
setFieldState('a~', state => (state.visible = false))
})
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'trigger').subscribe(
triggerState => {
setFieldState('a~', state => {
state.visible = triggerState.value
})
}
)
$(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, 'a').subscribe(fieldState => {
setFieldState('a-copy', state => {
state.value = fieldState.value
})
})
})
return (
<React.Fragment>
<CheckedField name="trigger" label="show/hide" />
<div>
<InputField label="a" name="a" />
</div>
<div>
<InputField label="a-copy" name="a-copy" />
</div>
</React.Fragment>
)
}
const App = () => {
return (
<Form actions={actions}>
<FormFragment />
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
useFormState
使用 useFormState 为自定义组件提供 FormState 扩展和管理能力
签名
(defaultState: T): [state: IFormState, setFormState: (state?: IFormState) => void]
用法
import React, { useRef } from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
VirtualField,
createFormActions,
createEffectHook,
useForm,
useFormState,
useFormEffects,
useFieldState,
LifeCycleTypes
} from '@formilyv1/react'
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const actions = createFormActions()
const FormFragment = props => {
const [formState, setFormState] = useFormState({ extendVar: 0 })
const { extendVar } = formState
return (
<div>
<button
onClick={() => {
setFormState({ extendVar: extendVar + 1 })
}}
>
add
</button>
<div>count: {extendVar}</div>
</div>
)
}
const App = () => {
return (
<Form actions={actions}>
<FormFragment />
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
useFieldState
Manage state of custom field by using
useFieldState
Signature
(defaultState: T): [state: IFieldState, setFieldState: (state?: IFieldState) => void]
import React, { useRef } from 'react'
import ReactDOM from 'react-dom'
import {
Form,
Field,
VirtualField,
createFormActions,
createEffectHook,
useForm,
useFormEffects,
useFieldState,
LifeCycleTypes
} from '@formilyv1/react'
const InputField = props => (
<Field {...props}>
{({ state, mutators }) => {
const loading = state.props.loading
return (
<React.Fragment>
{props.label && <label>{props.label}</label>}
{loading ? (
' loading... '
) : (
<input
disabled={!state.editable}
value={state.value || ''}
onChange={mutators.change}
onBlur={mutators.blur}
onFocus={mutators.focus}
/>
)}
<span style={{ color: 'red' }}>{state.errors}</span>
<span style={{ color: 'orange' }}>{state.warnings}</span>
</React.Fragment>
)
}}
</Field>
)
const changeTab$ = createEffectHook('changeTab')
const actions = createFormActions()
const TabFragment = props => {
const [fieldState, setLocalFieldState] = useFieldState({ current: 0 })
const { current } = fieldState
const { children, dataSource, form } = props
const ref = useRef(current)
const update = cur => {
form.notify('changeTab', cur)
setLocalFieldState({
current: cur
})
}
useFormEffects(($, { setFieldState }) => {
dataSource.forEach((item, itemIdx) => {
setFieldState(item.name, state => {
state.display = itemIdx === current
})
})
changeTab$().subscribe(idx => {
dataSource.forEach((item, itemIdx) => {
setFieldState(item.name, state => {
state.display = itemIdx === idx
})
})
})
})
ref.current = current
const btns = dataSource.map((item, idx) => {
console.log('current', current, ref.current)
const focusStyle =
idx === current ? { color: '#fff', background: 'blue' } : {}
return (
<button
style={focusStyle}
onClick={() => {
update(idx)
}}
>
{item.label}
</button>
)
})
return btns
}
const FormTab = props => {
return (
<VirtualField name="layout_tab">
{({ form }) => {
return <TabFragment {...props} form={form} />
}}
</VirtualField>
)
}
const App = () => {
return (
<Form actions={actions}>
<FormTab
dataSource={[
{ label: 'tab-1', name: 'username' },
{ label: 'tab-2', name: 'age' }
]}
/>
<div>
<InputField name="username" label="username" />
<InputField name="age" label="age" />
</div>
</Form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
useForm
get IForm instance
Signature
type useForm = <
Value = any,
DefaultValue = any,
EffectPayload = any,
EffectAction = any
>(
props: IFormProps<Value, DefaultValue, EffectPayload, EffectAction>
) => IForm
Usage
import { useForm } from '@formilyv1/react'
const FormFragment = () => {
const form = useForm()
return <div>{form.getFieldValue('username')}</div>
}
useField
get IFieldHook instance
Signature
type useField = (options: IFieldStateUIProps): IFieldHook
Usage
import { useField } from '@formilyv1/react'
const FormFragment = props => {
const { form, state, props: fieldProps, mutators } = useField({
name: 'username'
})
return (
<input
{...fieldProps}
{...props}
value={state.value}
onChange={mutators.change}
/>
)
}
useVirtualField
get IVirtualFieldHook instance
Signature
type UseVirtualField = (options: IVirtualFieldStateProps): IVirtualFieldHook
Usage
import { UseVirtualField } from '@formilyv1/react'
const FormFragment = props => {
const { form, state, props: fieldProps } = UseVirtualField({
name: 'username'
})
return (
<div style={{ width: fieldProps.width, height: fieldProps.heig