개발공부/React
[React] State 끌어올리기 (Lifting State Up)
jnnjnn
2024. 5. 30. 18:05
Lifting State Up이란 ?
React는 단방향 데이터 흐름의 원칙을 적용합니다 이에 따라 하위 컴포넌트는 상위 컴포넌트로부터 전달받은 데이터의 형태 혹은 타입이 무엇인지만 알 수 있습니다
따라서 하위 컴포넌트가 상위 컴포넌트의 상태를 업데이트하기 위해서는 상위 컴포넌트의 상태를 변경하는 함수 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행해야 합니다
이를 Lifting State Up이라고 합니다
useEffect 무한루프
댓글을 작성하면 새로고침을 하지 않아도 댓글 목록을 불러오는 기능을 추가하려고 했습니다
export function CommentComponent(props) {
return (
<Box>
<CommentWrite/>
<CommentList />
</Box>
);
}
export function CommentList({boardId}) {
const [commentList, setCommentList] = useState([]);
useEffect(() => {
axios
.get(`/api/comment/list/${boardId}`)
.then((res) => {
setCommentList(res.data);
})
}
}, [commentList]);
return (
<Box></Box>
);
}
댓글을 작성하고 댓글 목록을 불러오는 CommentComponent를 생성했습니다.
CommentWrite 에서 댓글을 작성하면 즉시 CommentList의 댓글 목록을 불러오기 위해 useEffect의 deps에 commentList를 넣었더니 무한 루프가 발생했습니다.
이유는 :
- 컴포넌트가 마운트되어 useEffect()가 실행된다
- useEffect가 실행되어 axios.get() 요청이 발생한다
- GET 요청 응답 결과로 commentList를 갱신한다 (res) => setCommentList(res.data)
- commentList의 참조값이 변경된다
- useEffect deps가 변경되어 useEffect()가 실행된다
- (반복) useEffect가 실행되어 axios.get() 요청이 발생한다
- ....
따라서 useEffect 실행 -> axios 요청 -> commentList 변경 -> useEffect 실행 -> axios 요청 -> commentList 변경 -> useEffect 실행... 의 무한 루프가 발생합니다
Lifting State Up 사용
useEffect의 무한루프 문제를 해결하기 위해 부모 컴포넌트가 리렌더링되면 자식 컴포넌트 역시 리렌더링되는 조건을 이용했습니다.
Lifting State Up
- CommentComponet(부모 컴포넌트)에 isProcessing이라는 state 변수를 선언하고 CommentWrite(자식 컴포넌트)에 setIsProcessing 함수를 전달해서 실행되게 합니다
- setIsProcessing으로 부모 컴포넌트의 state가 변경되어 부모컴포넌트와 자식 컴포넌트(CommentWrite와 CommentList)가 리렌더링됩니다.
export function CommentComponent(props) {
const [isProcessing, setIsProcessing] = useState(false);
return (
<Box>
<CommentWrite
isProcessing={isProcessing}
setIsProcessing={setIsProcessing}
/>
<CommentList
isProcessing={isProcessing}
/>
</Box>
);
}
export function CommentWrite({ setIsProcessing, isProcessing }) {
const [comment, setComment] = useState("");
function handleCommentSubmitClick() {
setIsProcessing(true);
axios
.post("/api/comment/add", { comment }).then().catch()
.finally(() => setIsProcessing(false));
}
return (
<Box>
<Textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
<Button
onClick={handleCommentSubmitClick}
>
댓글입력
</Button>
</Box>
);
}
export function CommentList({ boardId, isProcessing, setIsProcessing }) {
const [commentList, setCommentList] = useState([]);
useEffect(() => {
if (!isProcessing) {
axios
.get(`/api/comment/list/${boardId}`)
.then((res) => {
setCommentList(res.data);
})
.finally();
}
}, [isProcessing]);
return (
<Box>
{commentList.map((comment) => (
<Box>
<Box>{comment.nickName}</Box>
</Box>
<Box>
<Box>{comment.inserted}</Box>
</Box>
<Box>
<Box>{comment.comment}</Box>
</Box>
))}
</Box>
);
}
- CommentWrite에 setIsProcessing 함수를 전달합니다
- 댓글을 작성하면 setIsProcssing(true) 가 실행됩니다
- GET 요청 응답이 끝나면 setIsProcessing(false)가 실행됩니다
- CommentList의 useEffect() deps를 isProcessing으로 설정하여 isProcessing state가 변경될때마다 실행됩니다
- if(!isProcessing) 조건문의 존재로 handleCommentSubmitClick의 GET요청이 완료되었을 때만 CommentList의 GET요청이 발생합니다