-
-
Notifications
You must be signed in to change notification settings - Fork 278
feat: useWatch support dynamic names #758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,30 @@ | ||
import Form, { Field } from 'rc-field-form'; | ||
import React from 'react'; | ||
|
||
function WatchCom({ name }: { name: number }) { | ||
const data = Form.useWatch(name); | ||
return data; | ||
} | ||
import Input from './components/Input'; | ||
|
||
export default function App() { | ||
const [form] = Form.useForm(); | ||
const [open, setOpen] = React.useState<boolean>(true); | ||
const [keyName, setKeyName] = React.useState(true); | ||
|
||
const data = React.useMemo(() => { | ||
return Array.from({ length: 1 * 500 }).map((_, i) => ({ | ||
key: i, | ||
name: `Edward King ${i}`, | ||
age: 32, | ||
address: `London, Park Lane no. ${i}`, | ||
})); | ||
}, []); | ||
// const val = Form.useWatch(keyName ? 'name' : 'age', form); | ||
const val = Form.useWatch(values => values[keyName ? 'name' : 'age'], form); | ||
|
||
return ( | ||
<Form form={form}> | ||
<div className="App"> | ||
{/* When I made the switch, it was very laggy */} | ||
<button | ||
onClick={() => { | ||
setOpen(!open); | ||
}} | ||
> | ||
Switch | ||
</button> | ||
<WatchCom name={0} /> | ||
<WatchCom name={1} /> | ||
{open ? ( | ||
<div className="flex gap-[5px] flex-wrap"> | ||
{data?.map(item => { | ||
return ( | ||
<Field key={item.name} name={item.name}> | ||
<input /> | ||
</Field> | ||
); | ||
})} | ||
</div> | ||
) : ( | ||
<h2>some thing</h2> | ||
)} | ||
</div> | ||
<button | ||
onClick={() => { | ||
setKeyName(!keyName); | ||
}} | ||
> | ||
Switch {String(keyName)} | ||
</button> | ||
<Field name="name" initialValue="bamboo"> | ||
<Input /> | ||
</Field> | ||
<Field name="age" initialValue="light"> | ||
<Input /> | ||
</Field> | ||
{val} | ||
</Form> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,12 @@ import type { | |
InternalNamePath, | ||
NamePath, | ||
Store, | ||
WatchCallBack, | ||
WatchOptions, | ||
} from './interface'; | ||
import { isFormInstance } from './utils/typeUtil'; | ||
import { getNamePath, getValue } from './utils/valueUtil'; | ||
import { useEvent } from '@rc-component/util'; | ||
|
||
type ReturnPromise<T> = T extends Promise<infer ValueType> ? ValueType : never; | ||
type GetGeneric<TForm extends FormInstance> = ReturnPromise<ReturnType<TForm['validateFields']>>; | ||
|
@@ -23,19 +25,6 @@ export function stringify(value: any) { | |
} | ||
} | ||
|
||
const useWatchWarning = | ||
process.env.NODE_ENV !== 'production' | ||
? (namePath: InternalNamePath) => { | ||
const fullyStr = namePath.join('__RC_FIELD_FORM_SPLIT__'); | ||
const nameStrRef = useRef(fullyStr); | ||
|
||
warning( | ||
nameStrRef.current === fullyStr, | ||
'`useWatch` is not support dynamic `namePath`. Please provide static instead.', | ||
); | ||
} | ||
: () => {}; | ||
|
||
function useWatch< | ||
TDependencies1 extends keyof GetGeneric<TForm>, | ||
TForm extends FormInstance, | ||
|
@@ -123,56 +112,57 @@ function useWatch( | |
); | ||
} | ||
|
||
const namePath = getNamePath(dependencies); | ||
const namePathRef = useRef(namePath); | ||
namePathRef.current = namePath; | ||
|
||
useWatchWarning(namePath); | ||
|
||
useEffect( | ||
() => { | ||
// Skip if not exist form instance | ||
if (!isValidForm) { | ||
return; | ||
} | ||
|
||
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 = getWatchValue(values, allValues); | ||
const nextValueStr = stringify(newValue); | ||
|
||
// Compare stringify in case it's nest object | ||
if (valueStrRef.current !== nextValueStr) { | ||
valueStrRef.current = nextValueStr; | ||
setValue(newValue); | ||
} | ||
}); | ||
|
||
// TODO: We can improve this perf in future | ||
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 | ||
if (value !== initialValue) { | ||
setValue(initialValue); | ||
} | ||
|
||
return cancelRegister; | ||
}, | ||
|
||
// We do not need re-register since namePath content is the same | ||
// ============================== Form ============================== | ||
const { getFieldsValue, getInternalHooks } = formInstance; | ||
const { registerWatch } = getInternalHooks(HOOK_MARK); | ||
|
||
// ============================= Update ============================= | ||
const triggerUpdate = useEvent((values?: any, allValues?: any) => { | ||
const watchValue = options.preserve | ||
? (allValues ?? getFieldsValue(true)) | ||
: (values ?? getFieldsValue()); | ||
|
||
const nextValue = | ||
typeof dependencies === 'function' | ||
? dependencies(watchValue) | ||
: getValue(watchValue, getNamePath(dependencies)); | ||
|
||
if (stringify(value) !== stringify(nextValue)) { | ||
setValue(nextValue); | ||
} | ||
}); | ||
|
||
// ============================= Effect ============================= | ||
const flattenDeps = | ||
typeof dependencies === 'function' ? dependencies : JSON.stringify(dependencies); | ||
|
||
// Deps changed | ||
useEffect(() => { | ||
// Skip if not exist form instance | ||
if (!isValidForm) { | ||
return; | ||
} | ||
|
||
triggerUpdate(); | ||
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [isValidForm, flattenDeps]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 方法的话,那每次 render 就是新的方法吧? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 我想的是,如果 dependencies 是方法,那判断返回的值,stringify处理,如果改变了就 render There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
就是这么做的。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 哦,只是 triggerUpdate 执行了,但是 setValue 还是判断变没变 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 嗯 |
||
|
||
// Value changed | ||
useEffect(() => { | ||
// Skip if not exist form instance | ||
if (!isValidForm) { | ||
return; | ||
} | ||
|
||
const cancelRegister = registerWatch((values, allValues) => { | ||
triggerUpdate(values, allValues); | ||
}); | ||
|
||
return cancelRegister; | ||
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[isValidForm], | ||
); | ||
}, [isValidForm]); | ||
|
||
return value; | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.