📔 목차
1. 개요
2. Hook의 규칙
1. 개요
이번에 공부한 것은 Hook, React Hook의 규칙에 대한 것이다.
사실 React Hook의 규칙에 대해서는 예전, React를 처음 공부할 당시에
React 공식 문서 / Hook 규칙 파트를 본 적이 있었고, 공부한 내용을 정리까지 했었다.
그리고 이때 공부했던 것들은 시간이 지나면서 내 머릿속에서 점차 잊혀 갔고
최근에 사이드 프로젝트를 진행하면서 다시금 Hook 규칙에 대한 기억을 갱신하게 됐다.
이거는 실제로 내가 사이드 프로젝트를 진행할 때 봤었던 Error 메시지인데
React Query의 useQuery Hook을 onValid라는 임의의 함수에서 호출하는 코드를 짰다가 보게 됐다.
이는 React Hook을 함수 컴포넌트의 최상위 영역이 아닌 'onValid'라는
임의의 함수에서 호출을 시도했기에 발생된 문제라는 것을 공식문서를 통해서 확인할 수 있었다.
아주 예전에 Hook의 규칙에 대해서 이해하진 않고 보고 넘어가기만 했기 때문에
이번에도 그냥 넘기면 다음에 또 같은 실수를 할 것 같기도 하고
이 깨달음을 기록으로 남겨두지 않으면 아까울 것 같아서 이렇게 글을 쓰게 됐다.
개요는 여기까지만 하고 바로 본론으로 넘어가도록 하겠다.
2. Hook의 규칙
① Hook의 규칙
React Hook을 사용할 때 지켜야 할 규칙은 크게 두 가지이다.
1. React 함수의 최상위에서만 Hook 호출하기
- 반복문, 조건문 혹은 중첩된 함수 내에서 호출해선 안된다.
2. React 함수에서만 Hook 호출하기
- 일반 Javascript 함수 내부에서 Hook을 호출해선 안된다.
일단 실제로 React Hook을 일반 함수에서 사용했을 때 Error가 발생하는지 확인해 보자.
interface I_data {
charNm: string;
}
function getCharID(charNm: I_data){
return charNm;
}
function App(){
const {register, handleSubmit, setValue} = useForm<I_data>();
const [CharId, setCharId] = useState("");
const onValid = (data: I_data) => {
const {isLoading, data: charID} = useQuery(
"charInfo",
() => getCharID(data.charNm)
);
if(charID){
setCharId(charID?.ocid);
setValue("charNm", "");
} else{
alert("입력하신 닉네임을 다시 확인해주세요.");
setValue("charNm", "");
}
}
return (
<form onSubmit={handleSubmit(onValid)}>
<input
type="text"
placeholder="닉네임을 입력해주세요."
autoComplete="off"
{...register("charNm", {required: true})}
/>
<button>검색</button>
</form>
);
}
이전 파트, 개요 부분에서 예시로 보여줬던 Hook의 규칙과 관련된 Error가 발생했던 코드이다.
Nexon Open API의 메이플스토리 캐릭터 고유 Id를 제공하는 API 사용하려면
사용자가 입력한 텍스트, charNm이 필요했고 이를 위해서 'onValid' 함수 내부에서 useQuery() Hook 사용했다.
Fetch 함수는 인자로 charNm을 받고, 전달받은 charNm을 open api query의 parameter 사용
메이플스토리 인 게임 내에 charNm과 동일한 닉네임의 캐릭터가 있다면
해당 캐릭터의 고유 Id를 받아오고, 이를 결과 값으로 return 하는 함수이다.
코드를 작성할 당시에는 이렇게 쓰면 될 것 같았지만, 예상치 못한 Error를 보게 됐다.
앞에서 보여준 Error 메시지가 이때 당시에 내가 봤던 것이다.
결과적으로 Hook의 규칙을 어겼기 때문에 이런 에러가 발생했다는 것을 알게 됐고
(Hook은 조건문, 반복문 혹은 중첩된 일반 js 함수 내부에서 호출하면 안 된다)
굳이 useQuery Hook을 고집할 필요는 없다는 생각이 들어서
onValid 함수 내부에 api data fetch 하는 코드를 추가하는 식으로 해결할 수 있었다.
그러면 왜 React Hook을 조건문이나 반복문 또는 일반 함수에서 사용하면 안 된다고 하는 것일까?
예제를 통해서 확인해 보자.
② Hook 규칙 예제
function App(){
//1. 'Title' state 변수 생성
const [Title, setTitle] = useState("");
//2. 'Body' state 변수 생성
const [Body, setBody] = useState("");
//3. Effect 사용, Title과 Body의 내용 변경
useEffect(() => {
setTitle("제목1");
setBody("내용1");
});
//4. Effect 사용, 제목 업데이트
useEffect(() => {
document.title = Title;
});
//5. Effect 사용, localStorage에 Body에 저장된 문자열 저장
useEffect(() => {
localStorage.setItem("BodyText", Body);
});
return (...);
}
상단에 예시로 제시한 소스코드를 통해서도 알 수 있는 것은 하나의 컴포넌트에서
useState, useEffect와 같은 Hook을 여러 개 사용할 수 있다는 것이다.
위의 예제에서 useState Hook을 2번 사용했는데
이때 각 useState Hook이 어떤 state에 연결되는지 파악하기 위해서
React는 각 Hook이 호출된 순서를 참고해서 파악한다.
위의 소스코드에서 각 Hook의 호출 순서는 일정하기 때문에 문제없이 동작한다.
그러면 이번엔 코드를 살짝 수정해 보자.
function App(){
//1. 'Title' state 변수 생성
const [Title, setTitle] = useState("");
//2. 'Body' state 변수 생성
const [Body, setBody] = useState("");
//3. 'Title === "", useEffect Hook 실행
if (Title === ""){
useEffect(() => {
setTitle("제목1");
setBody("내용1");
});
}
//4. Effect 사용, 제목 업데이트
useEffect(() => {
document.title = Title;
});
//5. Effect 사용, localStorage에 Body에 저장된 문자열 저장
useEffect(() => {
localStorage.setItem("BodyText", Body);
});
return (...);
}
처음 App이 랜더링 될 때는 Title= ""로, 조건식의 결과는 true가 된다. (Title === "")
따라서 첫 번째 useEffect Hook이 실행, Title, Body state의 값이 수정된다.
다만 이후 두 번째 랜더링이 진행된다면, Title = "제목1"이므로 조건식의 결과는 false
첫 번째 useEffect Hook이 실행되지 않고, 바로 다음 useEffect Hook을 실행시킨다.
결국 Hook의 호출 순서가 달라져서, React에서 각 useState Hook 호출이
어떤 state와 연관됐는 지를 파악하는데 지장이 생긴다는 것이다.
이는 React의 컴포넌트의 상태 관리와 컴포넌트의 상태를 기반으로 랜더링을 하는
React의 랜더링 최적화 기능이 제대로 작동되지 않게 되는 문제가 발생할 수도 있다는 것이다.
결론을 말하자면, React Hook을 조건문, 반복문 아니면 일반 js 함수에서 호출 시
React가 Hook의 호출 순서를 기반으로 컴포넌트의 상태를 추적하는 것에 지장이 생기고
결과적으로 state의 값이 엉뚱한 값으로 업데이트되거나, 상태 관리가 깨질 수도 있고
더 나아가, React의 랜더링 최적화 기능도 제대로 작동하지 않을 가능성도 있기 때문에
물론 React에선 이를 방지하고자 useState, useEffect와 같은 Hook을
컴포넌트 내부, 최상위에서 호출하도록 시스템 적으로 제한을 뒀기 때문에
Hook 호출을 조건문, 반복문, 일반 JS 함수에서 시도하려고자 하면
개요 부분에서 보여줬던 에러 예시 이미지와 동일한 에러 메시지가 출력될 것이다.
📔 Reference
Hook의 규칙
📔 여담
첫번째 개요 파트는 실제 내 경험을 바탕으로 썼기 때문에
그나마 무난하게 쓸 수 있던 것 같은데, 두 번째 Hook의 규칙 / 예제 파트는 좀 엉망으로 쓴 것 같다.
왜 Hook을 컴포넌트 최상위가 아닌, 조건문이나 반복문, 함수에서 사용하면 안되는 지에 대한 이유를
써놓은 거기는 한데.. 공식 문서와 Chatgpt에게 물어본 결과 두 가지를 대충 훑어만 보고
머릿속에서 정리가 안된 상태에서 일단 빨리 끝내고 싶어서 무작정 써버리니 엉망이 된 느낌이다.
나중에 개인 프로젝트가 끝나고, 시간적으로도 정신적으로도 여유가 생기면
전면적으로 뜯어 고치는 걸로 해두고, 이번엔 이 정도에서 끝내는게 더 좋을 것 같다.
'React > React' 카테고리의 다른 글
[React] Checkbox 예제 (0) | 2025.02.22 |
---|---|
[React] Toggle Button 구현하기 (0) | 2024.08.07 |
[React Query] useQuery() (0) | 2024.08.03 |
[React] Plugin "react" was conflicted between "package.json (0) | 2024.06.03 |
[React] state (0) | 2024.01.29 |