UseEffect의 진짜 의미

YJS·2023년 9월 2일
1
post-thumbnail

😖문제 상황

리액트로 프론트 작업을 진행하는 도중 api에서 받아오는 값들 중 일부가 화면에 제대로 렌더링 되지 않는 이슈가 발생했다.

문제 코드 )

export default function Test() {
  const router = useRouter();
  const id = String(router.query.id || "");
  const methods = useForm({
    mode: "onChange",
  });

  const {
    register,
    handleSubmit,
    getValues,
    reset,
    formState: { errors },
  } = methods;

  const { data } = useTestDetailQuery(id);
  const { mutateAsync } = useTestMutation({ id: id });
  
  useEffect(() => { 
    setTitle(
      data?.title ? data.title: ""
    );
  }, [data]);
  
  return (
    <>
	<TableRow>
    	<StyledTableCellHead className="require">
          	제목
    	</StyledTableCellHead>
    	<StyledTableCell>
    		<StyledTextField
    		variant="standard"
    		defaultValue={data?.title || ""}
			fullWidth
			size="small"
			required={true}
			multiline
			{...register("title", {
 				maxLength: {
    			value: 20,
    			message:
    			"제목은 20자 이내로 입력 가능합니다.",
  				},
			})}
			error={!!errors["title"]}
			helperText={
  			errors["title"] && `${errors["title"]?.message}`
			}
			/>
		</>
	);
  }

🧐문제의 원인 파악

문제의 원인은 react hook과 useState, useEffect를 이해하지 못하고 무지성으로 코드를 작성한데에 있었다.

첫번째, useEffect가 컴포넌트의 변화를 감지하고 재렌더링해주는 함수라고 잘못 이해하고 있었다.
두번째, useState를 어느 상황에 사용해야하는지 혼동하고 있었다.
세번째, 리액트의 의존성 감지와 초기화에 대해 이해하지 못하고 있었다.

그렇다면 useEffect는 어떤 경우에 사용하고 useState과는 어떤 차이가 있으며 리액트는 어떻게 컴포넌트를 초기화하고 랜더링하는 것일까?

🤓문제 해결 과정

step1. useEffect 올바르게 이해하기

useEffect란?
useEffect는 컴포넌트 렌더링 이후에 비동기적으로 실행되는 함수로, 이를 활용하여 여러 가지 작업을 할 수 있습니다. 예를 들어, 특정 상태가 변경되었을 때 API 호출을 하거나 DOM 조작을 하는 등의 작업을 useEffect 내에서 수행할 수 있습니다.
useEffect는 두 번째 인자로 전달된 배열에 포함된 의존성(dependency)들이 변경될 때마다 호출됩니다. 만약 두 번째 인자로 빈 배열([])을 전달하면, useEffect는 컴포넌트가 처음 렌더링될 때 한 번만 호출되고, 그 이후에는 다시 호출되지 않습니다. 이는 useEffect를 컴포넌트가 마운트되었을 때만 실행하고, 어떠한 의존성도 감지하지 않는다는 의미입니다.

하지만 useEffect는 컴포넌트의 상태를 직접적으로 감시(watch)하는 것이 아니다. 즉, useEffect의 두 번째 인자로 전달한 배열에 특정 상태를 넣어놓더라도, 해당 상태가 변경되었을 때 useEffect가 자동으로 실행되는 것이 아니라는 것이다.
따라서, 내 코드에서 useEffect(() => {...}, [title])과 같이 title을 의존성 배열에 포함시켜도, title이 변경되었을 때 useEffect가 자동으로 호출되지는 않는다. 그저 title이 변경되었을 때 useEffect를 재실행하도록 설정해둔 것이다.

🧐이쯤되니 이게 무슨 소리인가? 싶을 수 있다. (내가 맨 처음에 그랬다.)

useEffect는 직접적으로 호출되는 함수가 아니라, 컴포넌트가 렌더링될 때 자동으로 실행되는 함수이다. 이것은 컴포넌트의 라이프사이클에 속하는 개념인데
useEffect는 기본적으로 컴포넌트가 처음 렌더링될 때와 의존성 배열에 명시된 상태들이 변경될 때 실행된다. 따라서, 의존성 배열에 포함된 상태가 변경되면 useEffect가 재실행된다. 이는 상태가 변경되었을 때 자동으로 실행되는 것을 의미한다.

즉, 사용자의 input이 변경되어서 상태가 바뀌면 useEffect가 자동으로 호출되는게 아니라 리액트 특성상 컴포넌트가 재랜더링 되면서 useEffect가 실행되는건데 그게 마치 title의 값이 변경될때마다 랜더링이 일어나니까 useEffect가 자동으로 실행되는것처럼 느껴질 수 있다는 것이다.

⭐️⭐️⭐️⭐️⭐️ 결론 ⭐️⭐️⭐️⭐️⭐️

useEffect는 컴포넌트가 렌더링될 때마다 실행되는 것이 아니라, 의존성 배열에 명시된 상태들이 변경되었을 때에만 실행된다.
따라서, 사용자의 input이 변경되어 title 상태가 바뀌면 컴포넌트가 다시 렌더링되고, 그 때 useEffect가 실행되는 것이다. 이런 특성 때문에 "사용자의 input이 변경될 때마다 useEffect가 자동으로 호출된다"라는 착각이 발생할 수 있다. 하지만 실제로는 사용자의 input이 변경되면 컴포넌트가 렌더링되고, 렌더링 시 useEffect의 의존성 배열에 명시된 상태가 변경되었을 때에만 useEffect가 실행되는 것이다.

step2. reset 함수 사용하기

reset이란?
reset이란 React Hook Form에서 제공하는 함수로, 폼(form)의 입력 요소들을 초기화하는데 사용됩니다. 이 함수를 호출하면 폼의 모든 입력 요소들의 값을 초기화하고, 에러 메시지들도 초기화하여 다시 빈 상태로 만듭니다. 기본적으로 reset 함수를 호출하면 모든 필드들의 값을 null로 설정하게 됩니다. reset 함수는 옵션을 설정하여 폼을 초기화할 때 특정 필드만 초기화하거나, 특정 필드에 원하는 값을 설정할 수도 있습니다. reset 함수의 옵션은 아래와 같이 사용할 수 있습니다:
1. 전체 폼 초기화: reset() 모든 필드의 값을 null로 초기화하고 에러 메시지를 초기화합니다.
2. 특정 필드 초기화: reset({ field: "fieldName" }) 특정 필드(fieldName)의 값을 null로 초기화하고 에러 메시지도 초기화합니다.
3. 특정 필드에 원하는 값을 설정하여 초기화: reset({ field: "fieldName", value: "customValue" }) 특정 필드(fieldName)의 값을 customValue로 설정하여 초기화하고 에러 메시지도 초기화합니다.

reset 함수를 호출할 때 주의할 점은 setValue 함수를 사용하여 직접 필드 값을 설정하고 싶을 때에도 reset을 사용하면 초기화된 값이 덮어씌워질 수 있다는 것이다. 이런 경우에는 필드별로 setValue를 사용하여 원하는 값을 설정해주어야 한다.

step3. useState 함수 완벽히 이해하기

useState이란?
useState이란 React의 함수형 컴포넌트에서 상태(state)를 관리하기 위해 사용하는 훅. useState 훅은 첫 번째 인자로 초기값을 받고, 배열 형태로 반환됩니다. 두번째 원소는 상태를 업데이트 하는 함수이다. 이를 통해서 상태 변수를 선언하고 초기값을 설정한 뒤 상태를 관리할 수 있는 함수를 활용하여 해당 컴포넌트의 상태를 업데이트한다.

	const [title, setTitle] = useState("");

위의 코드를 보면 useState을 사용하여 title 상태 변수를 선언하고, 초기값으로 빈 문자열("")을 설정했다.
첫 번째 원소는 상태 변수 자체이고(여기서는 title), 두 번째 원소는 상태를 업데이트하는 함수(여기서는 setTitle)이다.
그래서 위의 코드는 title이라는 상태 변수를 선언하고 초기값으로 빈 문자열을 설정한 뒤, 이 상태를 관리할 수 있는 setTitle이라는 함수를 얻어온 것이다.

💡 그렇다면 useEffect와 useState는 어떻게 다른것인가?

useEffect를 사용하는 이유는 상태(state)가 변경될 때마다 특정 동작을 수행하기 위해서입니다. onChange 이벤트로 setTitle를 호출하고 있는 코드를 보면, 사용자가 텍스트를 입력할때마다 title 값을 업데이트하는 것을 확인할 수 있습니다. 이렇게 상태가 변경되는 것에 반응하여 특정 동작을 수행하고 싶을 때 useEffect를 사용할 수 있습니다.
하지만 useEffect를 사용하는 이유가 주로 상태 변경에 대한 부수 효과를 처리하기 위함이기 때문에, 그냥 onChange에 의해 바로 setTitle를 호출하는 것으로도 원하는 동작을 수행할 수 있습니다.

즉, useEffect를 사용하지 않고도 실시간으로 title을 업데이트할 수 있다.
useEffect를 사용하지 않고도 바로 onChange에서 setTitle을 호출하고 title을 업데이트할 수 있다.

useEffect는 주로 컴포넌트가 렌더링될 때, 혹은 특정 상태가 변경될 때 동작하는 로직을 작성하는데 사용된다. 하지만 onChange 이벤트는 input 요소가 변경될 때마다 호출되는 이벤트이기 때문에 textChangeHandler 함수를 사용하여 원하는 동작을 수행할 수 있다.

따라서, 상태의 변경을 감지하고 해당 상태에 따른 로직을 수행해야 하는 경우에는 여전히 useEffect를 사용하는 것이 좋다. 코드를 더 깔끔하고 유지보수하기 쉽게 만들어주기 때문이다. 하지만 필요한 경우에만 사용하는 것이 좋으며, 단순한 상태 업데이트의 경우에는 별도의 textChangeHandler를 만들어 사용하는 것도 가능하므로 상황에 맞게 선택하면 된다.

useEffect에서 두 번째 인자로 data를 전달하면, data의 값이 변경될 때마다 useEffect가 호출된다. 예를 들어, useEffect(() => {...}, [data])라고 한다면, data에 있는 값이 변화할 때마다 함수 내부의 코드가 실행된다.
반면에 textChangeHandler를 사용하는 경우에는 onChange 이벤트가 발생할 때 해당 핸들러를 호출하도록 구현한다. 따라서 textChangeHandler 내부에서 원하는 상태를 업데이트하는 로직을 작성하면 된다. 이러한 방식은 useEffect보다 직접적으로 상태 변화를 감지하고 제어할 수 있다.

🤗해결 방법

해결된 코드)

export default function Test() {
  const router = useRouter();
  const id = String(router.query.id || "");
  const methods = useForm({
    mode: "onChange",
  });

  const {
    register,
    handleSubmit,
    getValues,
    reset,
    formState: { errors },
  } = methods;

  const { data } = useTestDetailQuery(id);
  const { mutateAsync } = useTestMutation({ id: id });

  useEffect(() => {
    if (id) reset(data);
  }, [data]);
  
  return (
    ...
    );
  }
profile
우당탕탕 개발 일기

0개의 댓글