@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
1,195 lines (1,063 loc) • 141 kB
Markdown
---
localeCode: en-US
order: 40
category: Input
title: Form
subTitle: Form
icon: doc-form
dir: column
---
## Form
- **Rerender on demand**, avoids unnecessary full-volume rendering, higher performance
- Easy to use, **simple structure**, avoids unnecessary hierarchical nesting
- Perfect accessibility support
- FormState / FieldState can also be easily obtained from outside the Form
Provides an external method to operate inside the form: formApi / fieldApi
- Support for encapsulating custom components into form controls, and you can quickly access your team's components through the extension mechanism provided by Form (through `withField` HOC)
- Support Form level / Field level assignment, verification (synchronous / asynchronous)
## Field
Semi encapsulates all form field component (Input、Select、Checkbox、DatePicker etc.) with `withField` once.
Taking over their data flow (`props.value` & `props.onChange`)
When in use, you need to import from the Form (note: only the control imported from the Form has data synchronization)
### Supported Field Component
- `Input`, `InputNumber`, `TextArea`, `Select`, `Checkbox`, `Radio`, `RadioGroup`, `Switch`, `DatePicker`, `TimePicker`, `Slider`, `InputGroup`, `TreeSelect`, `Cascader`, `Rating`, `AutoComplete`, `Upload`, `Label`, `ErrorMessage`, `Section`、`TagInput`
All mounted under Form and declared directly in `<Form.Input />` and `<Form.Select />` when used.
```javascript
import { Form } from '@douyinfe/semi-ui';
const FormInput = Form.Input;
const FormSelect = Form.Select;
const Option = FormSelect.Option;
```
The Field level component provided by Form, its `value` (or other properties specified by `valueKey`), onChange (or other callback functions specified by `onKeyChangeFnName`)
Properties are hijacked by Form, so
<Notice type="primary" title="Notice">
1. No longer need to manually bind the onChange event and update the value as controled component. But you can continue to listen onChange events for the latest values if you want
2. You cannot set the state of component with attributes such as `value`, `defaultValue`, `checked`, `defaultChecked`, etc. The default value can be set by Field's `initValue` or Form's `unitValues`
3. You should not modify the value of Form State directly, all changes to the data in the Form should be done by providing `formApi`, `fieldApi`
</Notice>
## Demos
### Various ways to declare form
Semi Form supports multiple writing at the same time.
#### Basic Usage
Add `field` property to each field component.
You can also set `label` properties for each field, by default is the same as field
`label` can be passed in a string directly, or declared in the form of an object, configure `extra`, `required`, `optional` and other attributes to deal with more complex scenarios
<Notice type='primary' title='Notice'>
The field attribute is required props
</Notice>
```jsx live=true dir="column"
import React from 'react';
import { Form, Tooltip } from '@douyinfe/semi-ui';
import { IconHelpCircle } from '@douyinfe/semi-icons';
() => (
<Form layout='horizontal'>
<Form.Input field='username' label='UserName' style={{ width: 80 }}/>
<Form.Input
field='password'
label={{
text: 'Password',
extra: <Tooltip content='More info xxx'><IconHelpCircle style={{ color: 'var(--semi-color-text-2)' }}/></Tooltip>
}}
style={{ width: 176 }}
/>
<Form.Select
field="role"
label={{ text: 'Role', optional: true }}
style={{ width: 176 }}
optionList={[
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
{ label: 'Guest', value: 'guest' },
]}
>
</Form.Select>
</Form>
);
```
#### Other declaration methods
When you need to get `formState`, `formApi`, `values`, etc. directly inside the Form structure, you can use the following writing
#### Via render props
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form render={({ formState, formApi, values }) => (
<>
<Form.Select field="role" label='Role' style={{ width: 120 }}>
<Form.Select.Option value="admin">Admin</Form.Select.Option>
<Form.Select.Option value="user">User</Form.Select.Option>
<Form.Select.Option value="guest">Guest</Form.Select.Option>
</Form.Select>
<Form.Input field='userName' label='UserName' />
<Form.Input field='password' label='Password' />
<code style={{ marginTop: 30 }}>{JSON.stringify(formState)}</code>
</>
)} layout='horizontal'>
</Form>
);
```
#### Via children function
declare children as a function that returns all field components
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form layout='horizontal'>
{
({ formState, values, formApi }) => (
<>
<Form.Select field="role" label='Role' style={{ width: 120 }}>
<Form.Select.Option value="admin">Admin</Form.Select.Option>
<Form.Select.Option value="user">User</Form.Select.Option>
<Form.Select.Option value="guest">Guest</Form.Select.Option>
</Form.Select>
<Form.Input field='userName' label='UserName' />
<Form.Input field='password' label='Password' />
<code style={{ marginTop: 30 }}>{JSON.stringify(formState)}</code>
</>
)
}
</Form>
);
```
#### Via props.component
Pass the entire internal structure directly in the form through `component` attribute.
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
class Demo extends React.Component {
constructor() { super(); }
render() {
const fields = ({ formState, formApi, values }) => (
<>
<Form.Input field='Role'/>
<Form.Input field='UserName' />
<Form.Input field='Password' />
<code style={{ marginTop: 30 }}>{JSON.stringify(formState)}</code>
</>
);
return <Form component={fields} layout='horizontal'/>;
}
}
```
### All supported field components
```jsx live=true dir="column"
import React from 'react';
import { Form, Col, Row, Button } from '@douyinfe/semi-ui';
import { IconUpload } from '@douyinfe/semi-icons';
class BasicDemoWithInit extends React.Component {
constructor() {
super();
this.state = {
initValues: {
name: 'semi',
business: ['ulikeCam'],
role: 'ued',
switch: true,
files: [
{
uid: '1',
name: 'vigo.png',
status: 'success',
size: '130KB',
preview: true,
url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/vigo.png'
},
{
uid: '2',
name: 'resso.jpeg',
status: 'validateFail',
size: '222KB',
percent: 50,
preview: true,
fileInstance: new File([new ArrayBuffer(2048)], 'resso.jpeg', { type: 'image/jpeg' }),
url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/Resso.png'
},
{
uid: '3',
name: 'dy.jpeg',
status: 'uploading',
size: '222KB',
percent: 50,
preview: true,
fileInstance: new File([new ArrayBuffer(2048)], 'dy.jpeg', { type: 'image/jpeg' }),
url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/dy.png'
}
]
}
};
}
render() {
const { Input, InputNumber, AutoComplete, Select, TreeSelect, Cascader, DatePicker, TimePicker, TextArea, CheckboxGroup, Checkbox, RadioGroup, Radio, Slider, Rating, Switch, TagInput, Section } = Form;
const { initValues } = this.state;
const plainOptions = ['A', 'B', 'C'];
const style = { width: '90%' };
const treeData = [
{
label: 'Asia',
value: 'Asia',
key: '0',
children: [
{
label: 'China',
value: 'China',
key: '0-0',
children: [
{
label: 'Beijing',
value: 'Beijing',
key: '0-0-0',
},
{
label: 'Shanghai',
value: 'Shanghai',
key: '0-0-1',
},
],
},
],
},
{
label: 'North America',
value: 'North America',
key: '1',
}
];
return (
<Form
initValues={initValues}
style={{ padding: 10, width: '100%' }}
onValueChange={(v)=>console.log(v)}
>
<Section text={'Basic Info'}>
<Row>
<Col span={12}>
<Input
field="name"
label="Name(Input)"
initValue={'mikeya'}
style={style}
trigger='blur'
/>
</Col>
<Col span={12}>
<DatePicker field="date" label='Date(DatePicker)' style={style} initValue={new Date()} placeholder='Choose data' />
</Col>
</Row>
<Row>
<Col span={12}>
<Select field="role" style={style} label='Role(Select)' placeholder='Choose role'>
<Select.Option value="qa">Quality Assurance</Select.Option>
<Select.Option value="rd">Software Engineer</Select.Option>
<Select.Option value="pm">Product Manager</Select.Option>
<Select.Option value="ued">Designer</Select.Option>
</Select>
</Col>
<Col span={12}>
<Select
field="business"
multiple
style={style}
placeholder='Choose application'
label="Application(Multiple Select)"
>
<Select.Option value="semi">Semi</Select.Option>
<Select.Option value="ulikeCam">UlikeCam</Select.Option>
<Select.Option value="xigua">BuzzVideo</Select.Option>
</Select>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Cascader
placeholder="Choose Area"
treeData={treeData}
field='area'
label='Area(Cascader)'
style={style}
>
</Form.Cascader>
</Col>
<Col span={12}>
<Form.TreeSelect
field="tree"
style={style}
label='Node(TreeSelect)'
placeholder='Select Service Node'
treeData={treeData}
filterTreeNode
>
</Form.TreeSelect>
</Col>
</Row>
<Row>
<Col span={12}>
<TagInput
field="product"
label='Product(TagInput)'
initValue={['abc', 'ulikeCam']}
placeholder='Type and choose product name'
style={style}
/>
</Col>
</Row>
<Row>
<Col span={24}>
<Form.Upload
field='files'
label='Files(Upload)'
action='//semi.design/api/upload'
>
<Button icon={<IconUpload />} theme="light">
Click to upload
</Button>
</Form.Upload>
</Col>
</Row>
</Section>
<Section text='Source Detail'>
<Row>
<Col span={12}>
<TextArea
style={style}
field='description'
label='Apply Reason(TextArea)'
/>
</Col>
<Col span={12}>
<CheckboxGroup
field="type"
label='Apply type(CheckboxGroup)'
initValue={['user', 'admin']}
rules={[
{ Requested: true }
]}
>
<Checkbox value="admin">admin</Checkbox>
<Checkbox value="user">user</Checkbox>
<Checkbox value="guest">guest</Checkbox>
<Checkbox value="root">root</Checkbox>
</CheckboxGroup>
</Col>
</Row>
<Row>
<Col span={12}>
<RadioGroup field="isMonopolize" label='Whether exclusive resources(Radio)'>
<Radio value={1}>Yes</Radio>
<Radio value={0}>No</Radio>
</RadioGroup>
</Col>
<Col span={12}>
<CheckboxGroup options={plainOptions} field="checkbox" label='Type(CheckboxGroup)' direction='horizontal'/>
</Col>
</Row>
<Row>
<Col span={12}>
<TimePicker field="time" label='End Time(TimePicker)' style={{ width: '90%' }}/>
</Col>
<Col span={12}>
<InputNumber field='number' label='Number of applications(InputNumber)' initValue={20} style={style}/>
</Col>
</Row>
<Row>
<Col span={12}>
<Slider field="range" label='Resource usage alarm threshold(%)(Slider)' initValue={10} style={{ width: '90%' }}/>
</Col>
<Col span={12}>
<Switch field='switch' label='Switch(Switch)'/>
</Col>
</Row>
<Row>
<Col span={12}>
<Rating field="rating" label='Satisfaction(Rating)' initValue={2} style={{ width: '90%' }}/>
</Col>
</Row>
</Section>
<Checkbox value="false" field="agree" noLabel={true}>
I have read and understood the relevant regulations(Checkbox)
</Checkbox>
<Button type="primary" htmlType="submit" className="btn-margin-right">Submit</Button>
<Button htmlType="reset">Reset</Button>
</Form>
);
}
}
```
### Field binding syntax
Every Field component must have a `field` property. This is how the form manages the state of this field.
See the field syntax section below for additional details on what you can pass in for field.
The field can be a simple string, can be contained`.`Or`[]`String that supports multi-level nesting
Below is an example of the field name and their mapping path in FormState
| Field | Resolution |
| ---------------------- | ---------------------------------- |
| username | formState.values.username |
| user\[0\] | formState.values.user\[0\] |
| siblings.1 | formState.values.siblings\[1\] |
| siblings\['2'\] | formState.values.siblings\[2\] |
| parents\[0\].name | formState.values.parents\[0\].name |
| parents\[1\]\['name'\] | formState.values.parents\[1\].name |
```jsx live=true dir="column"
import React from 'react';
import { Form, Row, Col, Toast, TextArea } from '@douyinfe/semi-ui';
() => (
<Form
onSubmit={values => Toast.info({ content: JSON.stringify(values) })}
>
{
({ formState, values, formApi }) => (
<Row>
<Col span={12}>
<Form.Input field='username' placeholder='Try input something'/>
<Form.Input field='user[0]' placeholder='Try input something'/>
<Form.Input field='siblings.1' placeholder='Try input something'/>
<Form.Input field="siblings['2']" placeholder='Try input something'/>
<Form.Input field='parents[0].name' placeholder='Try input something'/>
<Form.Input field="parents[1]['name']" placeholder='Try input something'/>
</Col>
<Col span={10} offset={1} style={{ marginTop: 12 }}>
<Form.Label text='formState.values in real time:'></Form.Label>
<TextArea value={JSON.stringify(formState.values)}></TextArea>
</Col>
</Row>
)
}
</Form>
);
```
### Form layout
- Vertical Layout: Arrange each field vertically (By default)
Semi Design recommends a vertical layout.
```jsx live=true dir="column"
import React from 'react';
import { Form, Button, Toast } from '@douyinfe/semi-ui';
() => {
const handleSubmit = (values) => {
console.log(values);
Toast.info('Submit Success');
};
return (
<Form onSubmit={values => handleSubmit(values)} style={{ width: 400 }}>
{({ formState, values, formApi }) => (
<>
<Form.Input field='phone' label='PhoneNumber' style={{ width: '100%' }} placeholder='Enter your phone number'></Form.Input>
<Form.Input field='password' label='Password' style={{ width: '100%' }} placeholder='Enter your password'></Form.Input>
<Form.Checkbox field='agree' noLabel>I have read and agree to the terms of service</Form.Checkbox>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<p>
<span>Or</span><Button theme='borderless' style={{ color: 'var(--semi-color-primary)', marginLeft: 10, cursor: 'pointer' }}>Sign up</Button>
</p>
<Button disabled={!values.agree} htmlType='submit' type="tertiary">Log in</Button>
</div>
</>
)}
</Form>
);
};
```
- Horizontal Layout: Arrange each field horizontally
You can use the horizontal layout by setting `layout='horizontal'`
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form layout='horizontal'>
<Form.Input field='phone' label='PhoneNumber' placeholder='Enter your phone number'></Form.Input>
<Form.Input field='password' label='Password' placeholder='Enter your password'></Form.Input>
</Form>
);
```
- Label Position, Label Align
You can control the position of the label in the Field and the direction of text alignment by setting `labelPosition`, `labelAlign`
```jsx live=true dir="column"
import React from 'react';
import { Form, Select, Checkbox, Radio } from '@douyinfe/semi-ui';
class BasicDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
labelPosition: 'left',
labelAlign: 'left',
};
}
render() {
const { labelPosition, labelAlign } = this.state;
const labelWidth = 120;
return (
<>
<div style={{ borderBottom: '1px solid var(--semi-color-text-3)', paddingBottom: 10 }}>
<Form
labelPosition='inset'
layout='horizontal'
initValues={{ labelPosition: 'left', labelAlign: 'left' }}
onValueChange={values => this.setState(values)}
>
<Form.Select
field='labelPosition'
style={{ width: 240 }}
label='Current Label Position:'
optionList={[
{ label: 'top', value: 'top' },
{ label: 'left', value: 'left' },
]}
/>
<Form.Select
field='labelAlign'
style={{ width: 240 }}
label='Current Label Align:'
optionList={[
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
]}
/>
</Form>
</div>
<Form
labelPosition={labelPosition}
labelWidth={labelWidth}
labelAlign={labelAlign}
style={{ padding: '10px', width: 600 }}>
<Form.Input
field="input"
label="PhoneNumber"
trigger='blur'
style={{ width: 200 }}
rules={[
{ required: true, message: 'required Error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'not semi' }
]}
/>
<Form.Switch label="Agree" field='agree'/>
<Form.InputNumber field='price' label='price' style={{ width: 200 }}/>
<Form.Select label="Name" field='name' style={{ width: 200 }}>
<Form.Select.Option value="mike">mike</Form.Select.Option>
<Form.Select.Option value="jane">jane</Form.Select.Option>
<Form.Select.Option value="kate">kate</Form.Select.Option>
</Form.Select>
<Form.CheckboxGroup label="Role" field='role' direction='horizontal'>
<Checkbox value="admin">admin</Checkbox>
<Checkbox value="user">user</Checkbox>
<Checkbox value="guest">guest</Checkbox>
<Checkbox value="root">root</Checkbox>
</Form.CheckboxGroup>
<Form.RadioGroup field="Sex">
<Radio value="1">man</Radio>
<Radio value="2">woman</Radio>
</Form.RadioGroup>
</Form>
</>
);
}
}
```
- A more complex layout.
You can also combine the `Row` and `Col` provided by the `Grid` to arrange the form structure as you want.
```jsx live=true dir="column"
import React from 'react';
import { Form, Row, Col } from '@douyinfe/semi-ui';
() => (
<Form
labelPosition='top'
getFormApi={this.getFormApi}
style={{ padding: '10px' }}>
<Row>
<Col span={8}>
<Form.Input
field="nickName1"
label="NickName"
style={{ width: '250px' }}
trigger='blur'
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'not semi' }
]}
/>
</Col>
<Col span={8}>
<Form.DatePicker field='date1' label='Valid Date' style={{ width: '250px' }}/>
</Col>
<Col span={8}>
<Form.Select label="Application" field='business1' style={{ width: '250px' }}>
<Form.Select.Option value="abc">Semi</Form.Select.Option>
<Form.Select.Option value="hotsoon">Vigo</Form.Select.Option>
<Form.Select.Option value="xigua">BussVideo</Form.Select.Option>
</Form.Select>
</Col>
</Row>
<Row>
<Col span={6}>
<Form.Input
field="nickName2"
label="NickName"
style={{ width: '200px' }}
trigger='blur'
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'not semi' }
]}
/>
</Col>
<Col span={6}>
<Form.DatePicker field='date2' label='Valid Date' style={{ width: '200px' }}/>
</Col>
<Col span={6}>
<Form.Select label="Application" field='business2' style={{ width: '250px' }}>
<Form.Select.Option value="abc">Semi</Form.Select.Option>
<Form.Select.Option value="hotsoon">Vigo</Form.Select.Option>
<Form.Select.Option value="xigua">BussVideo</Form.Select.Option>
</Form.Select>
</Col>
<Col span={6}>
<Form.Select field="role" style={{ width: '250px' }} label='Role(Select)' placeholder='Choose role'>
<Form.Select.Option value="qa">Quality Assurance</Form.Select.Option>
<Form.Select.Option value="rd">Software Engineer</Form.Select.Option>
<Form.Select.Option value="pm">Product Manager</Form.Select.Option>
<Form.Select.Option value="ued">Designer</Form.Select.Option>
</Form.Select>
</Col>
</Row>
</Form>
);
```
### wrapper Col / label Col
When you need to set a uniform layout for all Fields in a Form, you can set `wrapperCol` and `labelCol` on the `Form` to quickly generate the layout. No need to manually use `Row`, `Col` manual layout.
`wrapperCol`,`labelCol`Property Configuration Reference [Col components](/en-US/basic/grid#Col)
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form
wrapperCol={{ span: 20 }}
labelCol={{ span: 2 }}
labelPosition='left'
labelAlign='right'
>
<Form.Input field='name' style={{ width: 250 }} label='Name' placeholder='Input Name' trigger='blur' />
<Form.Select field="role" label='Role' placeholder='Choose Role' style={{ width: 250 }}>
<Form.Select.Option value="qa">Quality Assurance</Form.Select.Option>
<Form.Select.Option value="rd">Software Engineer</Form.Select.Option>
<Form.Select.Option value="pm">Product Manager</Form.Select.Option>
<Form.Select.Option value="ued">Designer</Form.Select.Option>
</Form.Select>
</Form>
);
```
### Remove automatically added Label
Form will automatically insert `Label` for Field control. If you don't need to automatically insert the Label module, you can turn off the automatic label insertion function by setting `noLabel=true` in the Field (at this time, the Field still has the ability to automatically display ErrorMessage, so the DOM structure is still different from the original component)
If you want to keep the DOM structure consistent with the original component, you can use `pure=true`. At this time, the DOM structure will not change except that the data flow is taken over (you need to be responsible for the rendering of ErrorMessage, and it cannot be used by formProps.wrapperCol property impact)
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form onSubmit={(values) => console.log(values)} style={{ width: 400 }}>
<Form.Input
field='name'
label='Name'
trigger='blur'
noLabel={true}
style={{ width: 250 }}
validator={val => val !== 'semi' ? 'not semi' : '' }
placeholder='Type your name'
/>
<Form.Input field='purename' pure placeholder='DOM same as origin Input component'/>
</Form>
);
```
### Embedded Label
A Label can be inlined in a field control by setting labelPosition to `inset`. Components currently supporting this feature include `Input`, `InputNumber`, `DatePicker`, `TimePicker`, `Select`, `TreeSelect`, `Cascader`, `TagInput`
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form labelPosition='inset' layout='horizontal'>
<Form.Input field='name' label='Name' trigger='blur' style={{ width: 250 }} initValue='semi'/>
<Form.Select field="role" label='Role' style={{ width: '250px' }} initValue='rd'>
<Form.Select.Option value="operate">operate</Form.Select.Option>
<Form.Select.Option value="rd">rd</Form.Select.Option>
<Form.Select.Option value="pm">pm</Form.Select.Option>
<Form.Select.Option value="ued">ued</Form.Select.Option>
</Form.Select>
<Form.DatePicker field="date" label='StartDate' style={{ width: '250px' }} initValue={new Date()}>
</Form.DatePicker>
</Form>
);
```
### Export Label, ErrorMessage use
When the built-in Label and ErrorMessage layout does not meet the business requirements, you need to combine the positions yourself, but you want to use the default styles of Label and ErrorMessage directly.
you can import them from the `Form` module, and combine `Form.Label` / `Form.ErrorMessage` by yourself.
For details of their API, refer to [Label](#Form.Label) / [ErrorMessage](#Form.ErrorMessage)
```jsx
import { Form } from '@douyinfe/semi-ui';
const { Label, ErrorMessage } = Form;
```
### Use Form.Slot
When your custom component needs to maintain the same layout style as the Field component, you can place your custom component in `Form.Slot`
`labelWidth`, `labelAlign`, `wrapperCol`, `labelCol` set on the Form component automatically acts on `Form.Slot`
For the Slot property configuration, refer to [Form.Slot](#Form.Slot)
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
class AssistComponent extends React.Component {
render() {
return (
<Form
onChange={v=>console.log(v)}
onSubmit={v=>console.log(v)}
style={{ width: 600 }}
labelPosition='left'
labelWidth={100}
>
<Form.Input field='effectName' label='EffectName' style={{ width: 250 }}/>
<Form.ErrorMessage />
<Form.Slot label={{ text: 'SlotA' }}>
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
{`I'm Semi Form SlotA, a custom ReactNode`}
</div>
</Form.Slot>
<Form.Slot label={{ text: 'SlotB', width: 160, align: 'right' }}>
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
{`I'm Semi Form SlotA, i have different labelWidth and textAlign.`}
</div>
</Form.Slot>
</Form>
);}
}
```
### Use helpText、extraText set prompt information
You can place custom prompt information through `helpText`, and display it in the same block as the verification information (error). When both have values, the verification information will be displayed first.
Additional prompt information can be placed through `extraText`. When the error message and prompt text need to appear at the same time, this configuration can be used. It is always displayed and located after helpText/error
When `validateStatus` is passed in, the UI style corresponding to the value of validateStatus will be displayed first. If not passed in, the internal verification status of the field shall prevail.
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => {
const [helpText, setHelpText] = useState('');
const [validateStatus, setValidateStatus] = useState('default');
const formRef = useRef();
const validator = (val, values) => {
if (!val) {
setValidateStatus('error');
return <span>Password can not be blank</span>;
} else if (val && val.length <= 3) {
setValidateStatus('warning');
setHelpText(<span style={{ color: 'var(--semi-color-warning)' }}>Password Strength: Weak</span>); // show helpText
return ''; // validate pass
} else {
setHelpText('');
setValidateStatus('success');
return '';
}
};
const random = () => {
let pw = (Math.random() * 100000).toString().slice(0, 5);
formRef.current.formApi.setValue('Password', pw);
formRef.current.formApi.setError('Password', '');
setHelpText('');
setValidateStatus('success');
};
return (
<Form
showValidateIcon={true}
ref={formRef}
onSubmit={(value) => console.log('submit success')}
onSubmitFail={(errors) => console.log(errors)}
>
<Form.Input
validator={validator}
field="Password"
validateStatus={validateStatus}
helpText={helpText}
extraText={
<div
style={{
color: 'var(--semi-color-link)',
fontSize: 14,
userSelect: 'none',
cursor: 'pointer'
}}
onClick={random}
>
Don't have a suitable password? Click to generate a random
</div>
}
></Form.Input>
</Form>
);
};
```
By configuring `extraTextPosition`, you can control the display position of extraText. Optional values `bottom`, `middle`
For example, when you want to display the extraText prompt information between the Label and Field component.
This attribute can be configured uniformly on the Form or individually on each Field. When passing in at the same time, the configuration of the Field shall prevail.
```jsx live=true dir="column"
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => {
const options = [
{ label: 'Lark Notification', value: 'lark' },
{ label: 'Email Notification', value: 'email' },
{ label: 'Banner Notification', value: 'notification' }
];
const notifyText = "When unchecked, the default is a red dot reminder, and the message enters the recipient's message list by default. For important notifications, you can check the corresponding notification methods at the same time";
const forceText = "For dialog notifications, you can specify that the message must wait for a specified amount of time before it can be marked as read.";
return (
<Form extraTextPosition='middle'>
<Form.CheckboxGroup
direction='horizontal'
field='notify'
label='Method to informe'
extraText={notifyText}
options={options}
/>
<Form.InputNumber field='force' label='Force read (optional)' placeholder='seconds' extraText={forceText} extraTextPosition='bottom'/>
</Form>
);
};
```
### Using Input Group
When you need to combine some fields to use, you can use `Form.InputGroup` to wrap them.
In Semi Form, when you using field components like `Form.Input`、`Form.Select`, Form will insert Label module automatically for them.
But usually, in`InputGroup` you only need a Label belonging to the entire Group.
You can set the label property in the `InputGroup` to insert a Label belonging to the Group
`label` configurable properties, see [Label](#Form.Label)
```jsx live=true dir="column"
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
() => (
<Form onSubmit={(values) => console.log(values)} labelPosition='top' style={{ width: 400 }}>
<Form.InputGroup label={{ text: (<span>PhoneNumber</span>), required: true }} labelPosition='top'>
<Form.Select style={{ width: 150 }} field='phonePrefix' initValue='+86' rules={[{ required: true }]} showClear>
<Form.Select.Option value='+1'>USA +1</Form.Select.Option>
<Form.Select.Option value='+86'>China +86</Form.Select.Option>
<Form.Select.Option value='+81'>Japan+81</Form.Select.Option>
</Form.Select>
<Form.Input initValue='18912345678' style={{ width: 250 }} field='phoneNumber' rules={[{ required: true }]} showClear/>
</Form.InputGroup>
<Form.Input field='name' trigger='blur' initValue='Semi' label='Name'></Form.Input>
<Button htmlType='submit'>Submit</Button>
</Form>
);
```
### Form in the Modal pop-up layer
You can place the Form in Modal and load it as a popup.
When submitting, use `formApi.validate()` to centrally verify the Field
```jsx live=true dir="column"
import React from 'react';
import { Form, Modal, Select, Button, Row, Col } from '@douyinfe/semi-ui';
class ModalFormDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
};
this.showDialog = this.showDialog.bind(this);
this.handleOk = this.handleOk.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.getFormApi = this.getFormApi.bind(this);
}
showDialog() {
this.setState({ visible: true });
}
handleOk() {
this.formApi.validate()
.then((values) => {
console.log(values);
})
.catch((errors) => {
console.log(errors);
});
}
handleCancel() {
this.setState({ visible: false });
}
getFormApi(formApi) {
this.formApi = formApi;
}
render() {
const { visible } = this.state;
let message = 'Required';
return (
<>
<Button onClick={this.showDialog}>Open Dialog</Button>
<Modal
title="New"
visible={visible}
onOk={this.handleOk}
style={{ width: 600 }}
onCancel={this.handleCancel}
>
<Form
getFormApi={this.getFormApi}
>
<Row>
<Col span={7}>
<Form.Select
field='region'
label="Country/Region"
style={{ width: 120 }}
rules={[
{ required: true, message },
]}
optionList={[
{ label: 'China', value: 'China' },
{ label: 'USA', value: 'US' },
{ label: 'Europe', value: 'Europe' },
{ label: 'Japan', value: 'Japan' },
]}
>
</Form.Select>
</Col>
<Col span={17}>
<Form.Input
field='owner'
label="Owner"
trigger='blur'
rules={[
{ required: true, message },
]}
/>
</Col>
<Col span={7}>
<Form.Select
field='area'
label="Area"
placeholder='Choose Area'
style={{ width: 120 }}
rules={[
{ required: true, message },
]}
>
<Form.Select.Option value="China">China</Form.Select.Option>
<Form.Select.Option value="US">USA</Form.Select.Option>
<Form.Select.Option value="Europe">Europe</Form.Select.Option>
<Form.Select.Option value="Japan">Japan</Form.Select.Option>
</Form.Select>
</Col>
<Col span={17}>
<Form.Input
field='department'
label="Department"
trigger='blur'
rules={[
{ required: true, message },
]}
/>
</Col>
</Row>
</Form>
</Modal>
</>
);
}
}
```
### Configure initial values and verification rules
- You can configure check rules for each Field through `rules`
The verification library inside the Form is based on `async-validator`, and more configuration rules can be found in its [official documentation](https://github.com/yiminghe/async-validator)
- You can uniformly set the initial value for the entire form through the `initValues` of form, or you can set the initial value through `initValue` in each field (the latter has a higher priority)
- You can configure different verification trigger timings for each Field through `trigger`, and the default is `change` (that is, when onChange is triggered, the verification is performed automatically). Also supports `change`, `blur`, `mount`, `custom` or a combination of the above. After v2.42, it supports unified configuration through FormProps. If both are configured, FieldProps shall prevail
- You can use the `stopValidateWithError`` switch to decide whether to continue to trigger the validation of subsequent rules when the first rule that fails the validation is encountered. After v2.42, unified configuration through FormProps is supported. If both are configured, FieldProps shall prevail
```jsx live=true dir="column"
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
() => {
const initValues = {
name: 'semi',
shortcut: 'se'
};
const style = { width: '100%' };
const { Select, Input } = Form;
return (
<Form initValues={initValues}>
<Input
field="name"
style={style}
trigger='blur'
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'should be semi' },
{ validator: (rule, value) => Boolean(value && value.startsWith('se')), message: 'should startsWith se' }
]}
/>
<Input
field="shortcut"
style={style}
stopValidateWithError
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'should be semi' },
{ validator: (rule, value) => Boolean(value && value.startsWith('se')), message: 'should startsWith se' }
]}
/>
<Button htmlType='submit'>提交</Button>
</Form>
);
};
```
### Custom Validate (Form Level)
You can set a custom validation function for the `Form` as a whole. **Recommended:** use `validator` (`validateFields` is legacy but still compatible). It will be called on submit or when calling `formApi.validate()`.
#### Synchronous Validate
When validate success, you should return an empty string.
When validate fails, you should return the error message (Object, key is fieldName, value is the corresponding error message)
```jsx live=true dir="column"
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
class FormLevelValidateSync extends React.Component {
constructor() {
super();
this.syncValidate = this.syncValidate.bind(this);
}
syncValidate(values) {
const errors = {};
if (values.name !== 'mike') {
errors.name = 'you must name mike';
}
if (values.sex !== 'female') {
errors.sex = 'must be woman';
}
errors.familyName = [
{ before: 'before errror balabala ', after: 'after error balabala' },
'familyName[1] error balabala'
];
return errors;
}
render() {
return (
<Form validator={this.syncValidate} layout='horizontal'>
<Form.Input field='name' trigger='blur'></Form.Input>
<Form.Input field='familyName[0].before' trigger='blur'></Form.Input>
<Form.Input field='familyName[0].after' trigger='blur'></Form.Input>
<Form.Input field='familyName[1]' trigger='blur'></Form.Input>
<div style={{ display: 'flex', alignItems: 'flex-end' }}>
<Button type="primary" htmlType="submit" className="btn-margin-right">
Submit
</Button>
<Button htmlType="reset">reset</Button>
</div>
</Form >
);
}
}
```
#### Asynchronous Validate
For asynchronous validation, you should return a promise. In promise.then() you need to return the corresponding error message.
```jsx live=true dir="column"
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
class FormLevelValidateAsync extends React.Component {
constructor() {
super();
this.asyncValidate = this.asyncValidate.bind(this);
}
asyncValidate(values) {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
return sleep(2000).then(() => {
let errors = {};
if (values.name !== 'mike') {
errors.name = 'you must name mike';
}
if (values.sex !== 'female') {
errors.sex =