문제상황
React-Hook-Form을 사용하면서 api로 조회해온 값을 value에 세팅을 해주고 싶었다.
그래서 Input 안에 field.value(직접 값을 control함) 과 내가 조회해온 값을 value로 써주었다.
조회는 잘되었으나, 조회된 값이 'apple'일 경우, 모든 문자를 삭제하여 'a'만 남았을 때 한 번 더 삭제하면 'apple'로 되돌아가는 문제가 있었습니다.
input-text.tsx (공통 Input)
interface Props {
name: string;
title: string;
type?: string;
value?: any;
maxLength?: number;
loading?: boolean
value: string;
}
export const InputText = ({ name, title, type,value, maxLength ,loading, value}: Props) => {
const form = useFormContext();
const hasError = !!form.formState.errors[name];
return (
<FormField
control={form.control}
name={name}
render={({ field }) => {
return (
<FormItem>
<div className="flex items-center gap-4 border-x-[1px] border-b">
<div className="gap-2.5 border-slate-200 bg-slate-50 py-2">
<FormLabel
htmlFor={name}
className={cn(
"flex h-[30px] w-[115px] items-center gap-2.5 p-2.5 text-xs font-normal text-neutral-950",
)}
>
{title}
</FormLabel>
</div>
<FormControl>
<Input
{...field}
type={type || "text"}
id={name}
value={field.value || value}
onChange={field.onChange}
maxLength={maxLength || 50}
className={cn(
"w-[200px]",
"placeholder:text-neutral-400",
"placeholder:text-xs",
"placeholder:font-normal",
{ "border-rose-500": hasError },
)}
disabled={field.disabled || loading}
placeholder="내용을 입력해주세요."
/>
</FormControl>
<div className="flex gap-1">
{hasError && (
<AiOutlineExclamationCircle className="h-4 w-4 fill-rose-500" />
)}
<FormMessage className="text-xs font-normal text-rose-500" />
</div>
</div>
</FormItem>
);
}}
/>
);
};
page.tsx (실제 사용하는 페이지)
export const UserPutContainer = () => {
const router = useRouter();
const pathname = usePathname();
const splitedPathname = pathname.split("/user/");
const buttonRef = useRef<HTMLButtonElement | null>(null);
const form = useForm<UserPutFormType>({
resolver: zodResolver(UserPutFormSchema),
defaultValues: {
id: "",
password: "",
name: "",
email: "",
phoneNumber: "",
},
});
const { formState } = form;
const { dirtyFields } = formState; //boolean값으로 값이 있는지 없는지 조회
//사용자 상세조회 api
const {itemList} = useGetDetailUser({
userIdx: Number(splitedPathname[1])
})
const onSubmit = () => {
try {
if (buttonRef.current === null) return;
buttonRef.current.disabled = false;
toast.success("수정되었습니다.");
form.reset();
router.push("/user");
} catch (e) {
console.log("error:", e);
}
};
const onHasChangesClick = () => {
if (Object.keys(dirtyFields).length > 0) {
setIsOpenModal(true);
} else {
router.push("/user");
}
};
return (
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex h-10 items-center justify-between border border-x-[1px] border-slate-300 bg-slate-200 px-4 py-2.5">
<p className="text-sm font-medium text-black">회원 수정</p>
<div className="flex gap-2">
<Button
type="button"
variant={"outline"}
onClick={onHasChangesClick}
>
목록
</Button>
<Button type="submit" ref={buttonRef}>
수정
</Button>
</div>
</div>
<InputText name="id" title="아이디" maxLength={15} loading={isLoading} value={itemList.id} />
<InputText
name="password"
title="비밀번호"
type="password"
maxLength={15}
loading={isLoading}
value={itemList.password}
/>
<InputText name="name" title="이름" maxLength={15} loading={isLoading} value={itemList.name} />
<InputText name="email" title="이메일" type="email" loading={isLoading} />
<InputText name="phoneNumber" title="휴대폰 번호" maxLength={13} loading={isLoading} value={itemList.phoneNumber} />
</form>
</Form>
</>
);
};
해결과정
Input value를 제거하고 useEffect를 사용해서 form.reset을 이용해 초기값을 api에서 받는 값으로 초기 세팅해준다.
input-text.tsx (공통 Input)
interface Props {
name: string;
title: string;
type?: string;
value?: any;
maxLength?: number;
loading?: boolean
}
export const InputText = ({ name, title, type,value, maxLength ,loading}: Props) => {
const form = useFormContext();
const hasError = !!form.formState.errors[name];
return (
<FormField
control={form.control}
name={name}
render={({ field }) => {
return (
<FormItem>
<div className="flex items-center gap-4 border-x-[1px] border-b">
<div className="gap-2.5 border-slate-200 bg-slate-50 py-2">
<FormLabel
htmlFor={name}
className={cn(
"flex h-[30px] w-[115px] items-center gap-2.5 p-2.5 text-xs font-normal text-neutral-950",
)}
>
{title}
</FormLabel>
</div>
<FormControl>
<Input
{...field}
type={type || "text"}
id={name}
onChange={field.onChange}
maxLength={maxLength || 50}
className={cn(
"w-[200px]",
"placeholder:text-neutral-400",
"placeholder:text-xs",
"placeholder:font-normal",
{ "border-rose-500": hasError },
)}
disabled={field.disabled || loading}
placeholder="내용을 입력해주세요."
/>
</FormControl>
<div className="flex gap-1">
{hasError && (
<AiOutlineExclamationCircle className="h-4 w-4 fill-rose-500" />
)}
<FormMessage className="text-xs font-normal text-rose-500" />
</div>
</div>
</FormItem>
);
}}
/>
);
};
page.tsx (실제 사용하는 페이지)
export const UserPutContainer = () => {
const router = useRouter();
const pathname = usePathname();
const splitedPathname = pathname.split("/user/");
const buttonRef = useRef<HTMLButtonElement | null>(null);
const form = useForm<UserPutFormType>({
resolver: zodResolver(UserPutFormSchema),
defaultValues: {
id: "",
password: "",
name: "",
email: "",
phoneNumber: "",
},
});
const { formState } = form;
const { dirtyFields } = formState; //boolean값으로 값이 있는지 없는지 조회
//사용자 상세조회 api
const {itemList} = useGetDetailUser({
userIdx: Number(splitedPathname[1])
})
useEffect(() => {
if (itemList) {
form.reset({
id: itemList.id,
password: itemList.pwd,
name: itemList.name,
email: itemList.email,
phoneNumber: itemList.phoneNumber,
});
}
}, [itemList, form]);
const onSubmit = () => {
try {
if (buttonRef.current === null) return;
buttonRef.current.disabled = false;
toast.success("수정되었습니다.");
form.reset();
router.push("/user");
} catch (e) {
console.log("error:", e);
}
};
const onHasChangesClick = () => {
if (Object.keys(dirtyFields).length > 0) {
setIsOpenModal(true);
} else {
router.push("/user");
}
};
return (
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex h-10 items-center justify-between border border-x-[1px] border-slate-300 bg-slate-200 px-4 py-2.5">
<p className="text-sm font-medium text-black">회원 수정</p>
<div className="flex gap-2">
<Button
type="button"
variant={"outline"}
onClick={onHasChangesClick}
>
목록
</Button>
<Button type="submit" ref={buttonRef}>
수정
</Button>
</div>
</div>
<InputText name="id" title="아이디" maxLength={15} loading={isLoading} />
<InputText
name="password"
title="비밀번호"
type="password"
maxLength={15}
loading={isLoading}
/>
<InputText name="name" title="이름" maxLength={15} loading={isLoading} />
<InputText name="email" title="이메일" type="email" loading={isLoading} />
<InputText name="phoneNumber" title="휴대폰 번호" maxLength={13} loading={isLoading} />
</form>
</Form>
</>
);
};
'트러블슈팅' 카테고리의 다른 글
[Chart.js] Canvas is already in use. Chart with ID '0' must be destroyed before the canvas with ID 'myChart' can be reused. (0) | 2024.08.05 |
---|---|
TypeError: Cannot read properties of undefined (reading 'length') (0) | 2024.08.01 |
CORS Error를 만나다 (0) | 2024.07.25 |
Button 무한 클릭 시 api 호출 막기 (0) | 2024.07.23 |
Next.js에서 middleware 설정 (0) | 2024.07.12 |