본문 바로가기

트러블슈팅

React-hook-form value 값 설정

문제상황

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>
    </>
     );
};