Skip to content

Commit

Permalink
feat: useWatch support selector (#637)
Browse files Browse the repository at this point in the history
* feat: api

* feat: test

* feat: 代码优化

* feat: 添加显示声明类型支持

* feat: 下划线优化

* feat: 下划线优化

* feat: watchValue
  • Loading branch information
crazyair authored Nov 30, 2023
1 parent 75dbcb3 commit 8bbfd34
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 8 deletions.
3 changes: 3 additions & 0 deletions docs/demo/useWatch-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## useWatch-selector

<code src="../examples/useWatch-selector.tsx"></code>
37 changes: 37 additions & 0 deletions docs/examples/useWatch-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import Form, { Field } from 'rc-field-form';
import Input from './components/Input';

type FieldType = {
init?: string;
name?: string;
age?: number;
};

export default () => {
const [form] = Form.useForm<FieldType>();
const values = Form.useWatch(
values => ({ init: values.init, newName: values.name, newAge: values.age }),
{ form, preserve: true },
);
console.log('values', values);
return (
<>
<Form form={form} initialValues={{ init: 'init', name: 'aaa' }}>
name
<Field name="name">
<Input />
</Field>
age
<Field name="age">
<Input />
</Field>
no-watch
<Field name="no-watch">
<Input />
</Field>
values:{JSON.stringify(values)}
</Form>
</>
);
};
32 changes: 25 additions & 7 deletions src/useWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ function useWatch<TForm extends FormInstance>(
form?: TForm | WatchOptions<TForm>,
): GetGeneric<TForm>;

// ------- selector type -------
function useWatch<TForm extends FormInstance, TSelected = unknown>(
selector: (values: GetGeneric<TForm>) => TSelected,
form?: TForm | WatchOptions<TForm>,
): TSelected;

function useWatch<ValueType = Store, TSelected = unknown>(
selector: (values: ValueType) => TSelected,
form?: FormInstance | WatchOptions<FormInstance>,
): TSelected;
// ------- selector type end -------

function useWatch<TForm extends FormInstance>(
dependencies: NamePath,
form?: TForm | WatchOptions<TForm>,
Expand All @@ -86,8 +98,10 @@ function useWatch<ValueType = Store>(
form?: FormInstance | WatchOptions<FormInstance>,
): ValueType;

function useWatch(...args: [NamePath, FormInstance | WatchOptions<FormInstance>]) {
const [dependencies = [], _form = {}] = args;
function useWatch(
...args: [NamePath | ((values: Store) => any), FormInstance | WatchOptions<FormInstance>]
) {
const [dependencies, _form = {}] = args;
const options = isFormInstance(_form) ? { form: _form } : _form;
const form = options.form;

Expand Down Expand Up @@ -125,8 +139,15 @@ function useWatch(...args: [NamePath, FormInstance | WatchOptions<FormInstance>]
const { getFieldsValue, getInternalHooks } = formInstance;
const { registerWatch } = getInternalHooks(HOOK_MARK);

const getWatchValue = (values: any, allValues: any) => {
const watchValue = options.preserve ? allValues : values;
return typeof dependencies === 'function'
? dependencies(watchValue)
: getValue(watchValue, namePathRef.current);
};

const cancelRegister = registerWatch((values, allValues) => {
const newValue = getValue(options.preserve ? allValues : values, namePathRef.current);
const newValue = getWatchValue(values, allValues);
const nextValueStr = stringify(newValue);

// Compare stringify in case it's nest object
Expand All @@ -137,10 +158,7 @@ function useWatch(...args: [NamePath, FormInstance | WatchOptions<FormInstance>]
});

// TODO: We can improve this perf in future
const initialValue = getValue(
options.preserve ? getFieldsValue(true) : getFieldsValue(),
namePathRef.current,
);
const initialValue = getWatchValue(getFieldsValue(), getFieldsValue(true));

// React 18 has the bug that will queue update twice even the value is not changed
// ref: https://github.com/facebook/react/issues/27213
Expand Down
48 changes: 47 additions & 1 deletion tests/useWatch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,29 @@ describe('useWatch', () => {
const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form);
const more = Form.useWatch(['age', 'name', 'gender'], form);
const demo = Form.useWatch<string>(['demo']);

const values2 = Form.useWatch(values => ({ newName: values.name, newAge: values.age }), form);
const values3 = Form.useWatch<FieldType, { newName?: string }>(values => ({
newName: values.name,
}));

return (
<>{JSON.stringify({ values, main, age, demo1, demo2, demo3, demo4, demo5, more, demo })}</>
<>
{JSON.stringify({
values,
main,
age,
demo1,
demo2,
demo3,
demo4,
demo5,
more,
demo,
values2,
values3,
})}
</>
);
};

Expand Down Expand Up @@ -457,4 +478,29 @@ describe('useWatch', () => {

logSpy.mockRestore();
});
it('selector', async () => {
const Demo: React.FC = () => {
const [form] = Form.useForm<{ name?: string }>();
const nameValue = Form.useWatch(values => values.name, form);
return (
<div>
<Form form={form}>
<Field name="name" initialValue="bamboo">
<Input />
</Field>
</Form>
<div className="values">{nameValue}</div>
</div>
);
};

const { container } = render(<Demo />);
await act(async () => {
await timeout();
});
expect(container.querySelector<HTMLDivElement>('.values')?.textContent).toEqual('bamboo');
const input = container.querySelectorAll<HTMLInputElement>('input');
await changeValue(input[0], 'bamboo2');
expect(container.querySelector<HTMLDivElement>('.values')?.textContent).toEqual('bamboo2');
});
});

1 comment on commit 8bbfd34

@vercel
Copy link

@vercel vercel bot commented on 8bbfd34 Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.