When using antd
Form, it is a very common requirement to monitor the change of a certain field and render different UI according to different field values.
So in antd
Form, how to monitor the change of a certain field?
1. form.getFieldValue
Before antd@4.20.0
, assuming that we want to monitor the changes of the song
field, we can easily write code like this:
const [form] = Form.useForm();
const songValue = form.getFieldValue('song');
<Form form={form}>
<Form.Item label="Song" name="song">
<Input />
</Form.Item>
{songValue?.length > 0 && <div>Song: {songValue}</div>}
</Form>;
What’s wrong with using form.getFieldValue
?
The problem is that the value obtained by form.getFieldValue
does not trigger UI updates. Simply put, it is not a state (the behavior of Form has changed with the upgrade of antd
, and the behavior of old versions will not be discussed here).
But why sometimes, you can see the UI updated?
This is because in the actual business code, multiple interfaces may be requested (setState
multiple times), and the Form may be updated by the parent to trigger re-render (in short, we all know that the number of re-renders of a React component is very high. Uncontrollable, especially when the code is poorly written 😜).
So it is not songValue
that triggers the UI update, but in the new re-render, songValue
is also updated.
This is a point that is very prone to bugs. Maybe the UI is normal during development, but as mentioned above, “the number of re-renders is very uncontrollable”, maybe a certain re-render is not triggered, and the UI related to songValue
is also It will not be updated.
2. useState
We found a bug where the UI doesn’t update! So I naturally thought of: turn song
into a state.
const [form] = Form.useForm();
const [songValue, setSongValue] = useState('');
<Form form={form}>
<Form.Item label="Song" name="song">
<Input onChange={(event) => setSongValue(event.target.value)} />
</Form.Item>
{songValue?.length > 0 && <div>Song: {songValue}</div>}
</Form>;
What’s wrong with using useState
?
Put it simply, songValue
here is not responsive, when song
is updated using the built-in method of Form
, the UI related to songValue
will not be updated.
For example, when song
is related to props
changes, or related to interface data changes, when using form.setFieldsValue
, form.resetFields
, etc. to update form data, songValue
will not be updated.
At this time, it is necessary to trigger setSongValue
at the place where form.setFieldsValue
and so on are executed.
When form.setFieldsValue
is executed in a very high parent component, it is necessary to lifting state songValue
up. To avoid this trouble, we prefer to handle changes in the child component’s useEffect
.
Using useEffect
not only triggers an extra re-render, but what if there are many fields that need to be processed in multiple places? (common situation in actual development)
We need to add a lot of repetitive code, write it back and forth, and forget where it is not added, where it needs to be added, and where it does not need to be added. In the end, the update logic will become a mess.
3. Form.useWatch
At the beginning, it was emphasized before antd@4.20.0
, because from antd@4.20.0
, antd
Form added a new API Form.useWatch
to handle this situation.
At this point, songValue
can respond to the updates of form.setFieldsValue
, form.resetFields
, etc.
const [form] = Form.useForm();
const songValue = Form.useWatch('song', form);
<Form form={form}>
<Form.Item label="Song" name="song">
<Input />
</Form.Item>
{songValue?.length > 0 && <div>Song: {songValue}</div>}
</Form>;
What’s wrong with using Form.useWatch
? (Why is there still a problem!)
In fact, it is not a problem, the main thing is that the performance is not good. Because Form.useWatch
actually turns songValue
into a state, and then handles the form linkage internally.
But the problem with state is that it will trigger the re-render of the entire component and perform unnecessary diffs. If the component is large and listens to Input
for real-time input, this kind of performance consumption is terrible, and each key is one time. Full diff.
And this kind of re-render is actually meaningless, because we “precisely” know that we need to monitor the changes of the song
field, and update the "partial" UI according to the value of song
instead of updating the overall UI.
So is there a more elegant “partial update” solution?
4. Watch
, from Ant Plus 5
Ant Plus 5 (antx
) provides a Watch
component, which is dedicated to listening to changes in form fields and updating local UI requirements.
Using the antx
component can simplify the antd
Form code, then the code to listen to song
will be as follows:
import { Form, Watch, Input } from 'antx';
const [form] = Form.useForm();
<Form form={form}>
<Input label="Song" name="song" />
<Watch name="song">
{(songValue) => {
// Only update in this place,
// not trigger the whole component re-render every time
return songValue?.length > 0 && <div>Song: {songValue}</div>;
}}
</Watch>
</Form>;
By using Watch
, you can avoid the performance problem of Form.useWatch
non-stop full re-render, and at the same time, you don’t need to process the update logic in useEffect
.
Using Watch
, only the UI returned in the render props will be updated, and the entire component will not be linked to re-render continuously.
Online example → https://codesandbox.io/s/antx-v4hqw
5. Introducing Watch
props
Watch
can also use list
prop to watch multiple fields.
name
and list
are mutually exclusive, because name
(NamePath
) of antd
also supports array form, so list
is used to distinguish different meanings of arrays.
children
& onlyValid
are mutually exclusive with onChange
.
Use onlyValid
to get only "valid values" that are not undefined
in the children
function. And setState
can be executed in onChange
.
Welcome to the Watch
component of antx
.
For more information about Ant Plus 5, please check → https://github.com/nanxiaobei/ant-plus