24.11.24

강연주·2024년 11월 24일

📚 TIL

목록 보기
101/186

Console Error : A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components

  • 원인
  1. input 또는 textarea 요소가 value 속성을 사용했지만
    초기 값이 undefined이거나 설정되지 않은 경우.
  2. 처음에는 "uncontrolled component"로 렌더링되다가
    이후 "controlled component"로 전환될 때.

➡️ 즉, value 또는 checked 속성을 사용하면서 상태값을 제대로 초기화하지 않거나, 상태값이 없을 때 undefined가 전달된 경우 위의 에러가 발생한다.

근데 이제 controlled component와 uncontrolled component 중 하나를 선택할 때, 당장의 에러 해결뿐 아니라 장기적인 값의 활용까지 고려해야 해서 고민이다 이거죠🥹


수정전

const LabeledInput: React.FC<LabeledInputProps> = ({ id, name, label, type = "text", placeholder, required = true }) => {
  return (
    <>
      <div>
        <label htmlFor="{id}">{label}</label>
        <input id={id} name={name} type={type} placeholder={placeholder} required={required} />
      </div>
    </>
  );
};

수정후

const LabeledInput = React.forwardRef<HTMLInputElement, LabeledInputProps>(
  ({ id, name, label, type = "text", placeholder, required = true }, ref) => {
    return (
      <div>
        <label htmlFor={id}>{label}</label>
        <input id={id} name={name} type={type} placeholder={placeholder} required={required} ref={ref} />
      </div>
    );
  }
);

수정전

const MeetupForm = () => {
  const queryClient = useQueryClient();
  const token =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzMyMTg3MjE4LCJpYXQiOjE3MzIxODY5MTgsImp0aSI6ImVjZDc0ZDBiOGEyODRmODZhMGE3MjRmYjUzNTBkNmRkIiwidXNlcl9pZCI6Mn0.Oy6DifyBzOdwsQt3kGxKEicCockgcsCuWlWDAEnBFG0";

  const [isPublicChecked, setIsPublicChecked] = useState(true);
  const [isStartedAtChecked, setIsStartedAtChecked] = useState(false);
  const [isEndedAtChecked, setIsEndedAtChecked] = useState(false);
  const [isStartedAtNullChecked, setIsStartedAtNullChecked] = useState(false);
  const [isEndedAtNullChecked, setIsEndedAtNullChecked] = useState(false);
  
   const createMutation = useMutation<void, Error, Meetup>({
    mutationFn: createMeetup,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["meetups"] });
    },
  });

  const handleMeetupFormSubmit = (event: React.FormEvent) => {
    event.preventDefault();
    // const form = event.currentTarget as HTMLFormElement;

    const nameValue = (event.target as HTMLInputElement).value;
    const descriptionValue = (event.target as HTMLInputElement).value;
    const placeDescriptionValue = (event.target as HTMLInputElement).value;
    const placeValue = (event.target as HTMLInputElement).value;
    const startedAtValue = (event.target as HTMLInputElement).value;
    const endedAtValue = (event.target as HTMLInputElement).value;
    const adTitleValue = (event.target as HTMLInputElement).value;
    const adEndedAtValue = (event.target as HTMLInputElement).value;
    // const isPublicValue = (form.elements.namedItem("isPublic") as HTMLInputElement).checked;
    const imageValue = (event.target as HTMLInputElement).value;
    const categoryValue = (event.target as HTMLInputElement).value;

    const newMeetup = {
      // name: form.name.value,
      name: nameValue,
      description: descriptionValue,
      place: placeValue,
      placeDescription: placeDescriptionValue,
      startedAt: startedAtValue,
      endedAt: endedAtValue,
      adTitle: adTitleValue,
      adEndedAt: adEndedAtValue,
      // isPublic: isPublicValue,
      image: imageValue,
      category: categoryValue,
    };

    createMutation.mutate(newMeetup);
    // event.target.reset();
  };
  return (
    <>
      <div>
        <form onSubmit={handleMeetupFormSubmit}>
          <div>
            {/* <LabeledInput id="category" name="category" label="모임 성격" type="text" required /> */}
            <select>
              <option value="">{value}</option>
            </select>
            <LabeledInput id="title" name="title" label="모임 이름(랜덤 생성 버튼 필요)" type="text" required />
            <LabeledInput id="startedAt" name="startedAt" label="모임 시작 날짜" type="date" required />
            <LabeledInput id="endedAt" name="endedAt" label="모임 종료 날짜" type="date" required />
            <LabeledInput id="place" name="place" label="모임 지역" type="text" required />
            <LabeledInput id="placeDescription" name="placeDescription" label="모임 장소" type="text" placeholder="만날 곳의 대략적 위치를 적어주세요. 예) 강남역" required />
            <LabeledInput id="adTitle" name="광고글 제목" label="광고글 제목" type="text" minlength="2" maxlength="5" required />
            <LabeledInput id="adEndedAt" name="adEndedAt" label="광고 종료 날짜" type="date" required />
          </div>
          <div>
            <label htmlFor="description">광고글 설명</label>
            <textarea id="description" name="description" defaultValue=""></textarea>
          </div>
          <div>
            <label htmlFor="isPublic">광고글 공개하기</label>
            <input id="isPublic" type="checkbox" checked={isPublicChecked} onChange={() => setIsPublicChecked(!isPublicChecked)} />
          </div>
          <div>
            <button type="submit">모임 생성하기</button>
          </div>
        </form>
      </div>
      
        );
};

수정후

const MeetupForm = () => {
  const adTitleRef = useRef<HTMLInputElement>(null);

  const handleMeetupFormSubmit = (event: React.FormEvent) => {
    event.preventDefault();
    const adTitle = adTitleRef.current?.value || "";
    console.log("Submitted adTitle:", adTitle);
  };

  return (
    <form onSubmit={handleMeetupFormSubmit}>
      <LabeledInput id="adTitle" name="adTitle" label="광고글 제목" ref={adTitleRef} required />
      <button type="submit">모임 생성하기</button>
    </form>
  );
};

https://velog.io/@juno7803/React-useRef-200-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0


profile
아무튼, 개발자

0개의 댓글