개발공부/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요청이 발생합니다