how.... why... ์ ๋ฌ๋ผ ์..
๐จ ๊ฐ์ register name์ผ๋ก useWatch๋ฅผ ๋ ์ปดํฌ๋ํธ์์ ์คํ ์ ๋ watch ๊ฐ์ด ๋ค๋ฅธ ํ์ ๋ฐ์
๋ณด์ด๋ ๊ฑด ์ฌ๋ฐ๋ฅธ ๊ฐ์ธ๋ฐ submit ํ๋ ๋ค๋ฅธ ๊ฐ์ด ์ฐํ๋ค๊ณ ?!?!?!
์ฒซ ๋ฒ์งธ ๊ฐ์ค
: useWatch๋ฅผ ๊ฐ์ register name์ผ๋ก ๋ ๊ณณ์์ ์ฌ์ฉํ์ ๋ ๋ฌธ์ ๊ฐ ๋ ๊ฒ์ด๋ค.
react-hook-form github์์ useWatch
์ฝ๋๋ฅผ ํ์ธํ ์ ์๋ค. ๊ณผ์ฐ ์ค๋ณต register name์ด ๋ฌธ์ ๊ฐ ๋ ๊น?
// src/useWatch.ts
export function useWatch<TFieldValues extends FieldValues>(
props?: UseWatchProps<TFieldValues>,
) {
const methods = useFormContext();
const {
control = methods.control,
name, // register name
defaultValue,
disabled,
exact,
} = props || {};
const _name = React.useRef(name);
_name.current = name;
useSubscribe({
disabled,
subject: control._subjects.values,
next: (formState: { name?: InternalFieldName; values?: FieldValues }) => {
if (
shouldSubscribeByName(
_name.current as InternalFieldName,
formState.name,
exact,
)
) {
updateValue(
cloneObject(
generateWatchOutput(
_name.current as InternalFieldName | InternalFieldName[],
control._names,
formState.values || control._formValues,
false,
defaultValue,
),
),
);
}
},
});
const [value, updateValue] = React.useState(
control._getWatch(
name as InternalFieldName,
defaultValue as DeepPartialSkipArrayKey<TFieldValues>,
),
);
React.useEffect(() => control._removeUnmounted());
return value;
}
์ฝ๋๋ฅผ ๋ถ์ํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
useWatch
์ ๋๊ธด name
prop์ผ๋ก ref ๊ฐ์ฒด๋ฅผ ๋ฐํํด _name
์ ํ ๋นํ๋ค. name
prop์ผ๋ก ๋ณ๊ฒฝํ๋ค.shouldSubscribeByName
ํจ์์ _name.current
์ formState
์ name
์ ๋๊ฒจ true๋ฉด value๋ฅผ updateํ๋ค.shouldSubscribeByName
ํจ์ ์ฝ๋๋ ์ดํด๋ณด์.
// src/logic/shouldSubscribeByName.ts
import convertToArrayPayload from '../utils/convertToArrayPayload';
export default <T extends string | string[] | undefined>(
name?: T,
signalName?: string,
exact?: boolean,
) =>
!name ||
!signalName ||
name === signalName ||
convertToArrayPayload(name).some(
(currentName) =>
currentName &&
(exact
? currentName === signalName
: currentName.startsWith(signalName) ||
signalName.startsWith(currentName)),
);
์ฝ๋๋ฅผ ๊ฐ๋ตํ ๋ถ์ํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ํฉ์์ true๋ฅผ ๋ฐํํ๋ค.
๊ทธ๋ฌ๋๊น registerํ์ง ์์ ๊ฐ์ผ๋ก useWatch๋ฅผ ์ฌ์ฉ(์ดํ watch)ํ๋ ๊ฑด ๋ฌธ์ ๊ฐ ๋๊ฒ ์ง๋ง, ๊ฐ์ register name๊ฐ ๋ฌธ์ ๋๋ ์ฝ๋๋ ์๋ค. ๊ฐ์ register name์ผ๋ก ๋ ๊ณณ์์ watch ํด๋ ๋ฌธ์ ๊ฐ ๋์ง ์์ผ๋ฏ๋ก ์ด ๊ฐ์ค์ ํ๋ ธ๋ค.
๋ ๋ฒ์งธ ๊ฐ์ค
: setValue๊ฐ ์ ์์ ์ผ๋ก ์๋ํ์ง ์๋๋ค.
์์ ์ปดํฌ๋ํธ(Component B)์์๋ง setValue ๊ฒฐ๊ณผ๊ฐ ์ ์์ ์ผ๋ก ๋ฐ์๋์ด ๋ watch ๊ฐ์ด ๋ฌ๋๋ค.
์ ์์ ์ปดํฌ๋ํธ์์ setValue๋ก ๊ฐ์ ๋ณ๊ฒฝํด๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ watch ๊ฐ์ ๋ณ๊ฒฝ๋์ง ์์์๊น?
rhf discussions์์ ๋์ ๋น์ทํ ์ฌ๋ก๋ฅผ ๋ฐ๊ฒฌํ๋ค.
๋ค์๊ณผ ๊ฐ์ ๋ต๋ณ์ด ๋ฌ๋ ค์์๋ค.
useWatch์ setValue์ ์์์ ๋ฐ๋ผ watch๊ฐ ์๋๋๋ก ๋์ง ์์ ์ ์๊ณ , ์ด๊ฒ ์ ์์ด๋ผ๋ ๋ต๋ณ์ด์๋ค.
๋ถ๋ชจ์ ์์ ์ปดํฌ๋ํธ์์ ๊ฐ์ด ๋ค๋ฅด๊ฒ ์ฐํ ์ด์ ๊ฐ ์ด๊ฑฐ์๋ค!!
์ ์์๋ก ์งํ๋์๊ธฐ ๋๋ฌธ์ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ์์ ์ปดํฌ๋ํธ์ watch ๊ฐ์ด ๋ฌ๋๋ ๊ฒโฆ!
์ค์ ๋ก setTimeout ์ฝ๋ฐฑ์์ setValue๋ฅผ ์คํํ๋ ๋ ์ปดํฌ๋ํธ์์ ์๋ํ ๋๋ก ๊ฐ์ด ์ฐํ๋ค.
// ์์ ์ปดํฌ๋ํธ
export const ComponentB = () => {
...
useEffect(() => {
setTimeout(() => {
setValue("useWatchTest", getTestValue(query, room));
}, 0);
}, [room]);
...
return (...);
};
// ๋ถ๋ชจ ์ปดํฌ๋ํธ
export const ComponentA = () => {
...
const useWatchTest = useWatch({ control, name: "useWatchTest" });
const vaildateBookingsForm = () => {
console.log("??? useWatchTest", useWatchTest);
// ??? useWatchTest {a: 2, b: 1, c: 1} ๐ ์ฌ๋ฐ๋ฅธ ๊ฐ
};
...
return (...);
};
๊ฒฐ๋ก
: useWatch์ setValue๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ค๋ฉด, useWatch๊ฐ ๋จผ์ ์คํ๋๋๋ก ์์๋ฅผ ๋ณด์ฅํ์.
๋๋ ๋ถ๋ชจ์ปดํฌ๋ํธ๋ form์์ฑ๊ณผ submit๋ง ๊ด์ฌํ๋๋ก ํ๊ธฐ ์ํด ์์ ์ปดํฌ๋ํธ์์ ๋น๋๊ธฐ ๋ฐ์ดํฐ๋ก default ๊ฐ์ ๋ฐ๊ฟ์ค์ผ ํ ๋ setValue๋ฅผ ํ์ฉํ๊ธฐ์ ์ด๋ฒ ์ด์๋ฅผ ๊ฒช์๋ค. ๋ถ๋ชจ ์ปดํฌ๋ํธ์ watch๊ฐ ๋จผ์ ์คํ๋์ด setValue๋ก ๋ณ๊ฒฝ๋์ด์ผ ํ ๊ฐ์ด ๋ฐ์๋์ง ์์๊ธฐ ๋๋ฌธ!
setTimeout ์ฝ๋ฐฑ์์ setValue๋ฅผ ์คํํ๋๋ก ์์ ํ๋ ๋ฐฉ๋ฒ๋ ์๊ฒ ์ง๋ง, ๋์ ๊ฒฝ์ฐ์๋ default ๊ฐ์ ์ธํ ํ๊ธฐ ์ํ setValue์๊ธฐ์ ๋ค์๊ณผ ๊ฐ์ด ํด๊ฒฐํ๋ค.
// Component A
export const ComponentA = () => {
...
const bookingForm = useForm<{
useWatchTest: { a: number; b: number; c: number };
}>({
value: {
...DEFAULT_FORM_VALUES,
useWatchTest: getTestValue(room) ๐๐๐
}
});
...
return (...);
};
๋์ด์ผ๋ณด๋ ์์ ์ปดํฌ๋ํธ์์ default ๊ฐ์ ์ธํ
ํ๋ ค ํ ์์ฒด๊ฐ ์ข์ ์ ํ์ด ์๋์๋ ๊ฒ ๊ฐ๋ค. ๋ถ๋ชจ ์ปดํฌ๋ํธ์์๋ form ์์ฑ๊ณผ submit๋ง ํ๊ณ , ๊ฐ๊ฐ์ input ์ปดํฌ๋ํธ์์ validation๊ณผ value ํธ๋ค๋ง์ ๋ชจ๋ ์ฒ๋ฆฌํ๊ณ ์ ํ๋ค๊ฐ (๋๋ฌด ๋ชฐ์
ํ ๋๋จธ์ง) default value๋ ์ธํ
ํด๋ฒ๋ฆฐ ๊ฒ....
๊ทธ๋๋ ๋๋ถ์ ํ๋๋ฃจ ๋ง๋๋ฃจ ์ ์ฐ๋ react-hook-form์ ๋ด๋ถ ๊ตฌํ๋ ๋ค์ฌ๋ค๋ดค๋ค.
๋ถ๋ชจ-์์ ์ปดํฌ๋ํธ์ ๋ ๋๋ง ์์๋ ์ด๋ฒ ๊ธฐํ์ ๋ณต๊ธฐํ๋ค.
๋ฐฐ์ธ ์ ์ด ๋ง์ ์ด์์๋ค.....!!!
์์ ์ฝ๋๊ฐ ๊ถ๊ธํ์ ๋ถ๋ค๊ป...
Component A (๋ถ๋ชจ)const DEFAULT_FORM_VALUES = {
useWatchTest: {
a: 1,
b: 0,
c: 0,
}
}
// ๋ถ๋ชจ ์ปดํฌ๋ํธ
export const ComponentA = () => {
const { data: room, error: roomError } = useRoom(...);
const bookingForm = useForm<{
useWatchTest: { a: number; b: number; c: number };
}>({
defaultValues: DEFAULT_FORM_VALUES
});
const { handleSubmit, control } = bookingForm;
const useWatchTest = useWatch({ control, name: "useWatchTest" });
const vaildateBookingsForm = () => {
console.log("??? useWatchTest", useWatchTest);
// ??? useWatchTest {a: 1, b: 0, c: 0} ๐ฟ ์๋ชป๋ ๊ฐ (setValue๊ฐ ๋ฐ์๋์ง ์์ default value)
};
return (
<form onSubmit={handleSubmit(vaildateBookingsForm)}>
<FormProvider {...bookingForm}>
...
<ComponentB />
</FormProvider>
</form>
);
};
Component B (์์)
const getTestValue = (room: IRoom) => ({
a: room['default_a'], // 2
b: room['default_b'], // 1
c: room['default_c'], // 1
})
// ์์ ์ปดํฌ๋ํธ
export const ComponentB = () => {
const { data: room } = useRoom(...);
const { setValue } = useFormContext();
const useWatchTest = useWatch({ name: "useWatchTest" });
useEffect(() => {
setValue("useWatchTest", getTestValue(room));
}, [room]);
return <>...</>;
};