본문 바로가기

Front-End: Web/React.js

useMutation 사용 시 컴포넌트 리렌더링 줄이기

반응형

문제점?

로그인페이지에서 로그인 버튼을 누르면 모든 컴포넌트가 리렌더링되는 문제가 발생한다.

 

 

useMutation의 반환값들

export interface MutationResult<TData = any> {
    data?: TData | null;
    error?: ApolloError;
    loading: boolean;
    called: boolean;
    client: ApolloClient<object>;
}

이 중에서 loading 이 리렌더링을 발생시킨다.

따라서 useMutation을 작성한 컴포넌트 내에서 무조건 리렌더링이 발생한다.

컴포넌트의 리렌더링을 줄이고 싶다면, props를 전달해서 하위 컴포넌트에서 useMutation을 실행시키자.

 

프로젝트 적용하기

이전 코드

여기서 onLogin을 내려주는게 아니라, refs와 setErrorMessage를 내려주는 형태로 변경해야 한다.

// LoginPage.tsx
import LoginModalBody from "@components/molecules/Div/LoginModalBody";
import LoginModalFooter from "@components/molecules/Div/LoginModalFooter";
import LoginModalHeader from "@components/molecules/Div/LoginModalHeader";
import AuthModal from "@components/organisms/Modal/AuthModal";
import useLogin from "@hooks/query/useLogin";
import validateEmail from "@utils/validateEmail";
import { useCallback, useRef, useState } from "react";

const LoginPage = () => {
  const emailRef = useRef<HTMLInputElement>(null);
  const passwordRef = useRef<HTMLInputElement>(null);
  const refs = { emailRef, passwordRef };

  const [errorMessage, setErrorMessage] = useState("");

  const { mutate: login } = useLogin(setErrorMessage);

  const onLogin = useCallback(() => {
    if (!emailRef.current?.value || !passwordRef.current?.value) {
      return setErrorMessage("모든 값을 입력해주세요.");
    }
    if (!validateEmail(emailRef.current.value)) {
      return setErrorMessage("유효하지 않은 아이디입니다.");
    }
    if (passwordRef.current.value.length < 8) {
      return setErrorMessage("비밀번호는 8자리 이상이어야 합니다.");
    }
    setErrorMessage("");
    login({
      email: emailRef.current.value,
      password: passwordRef.current.value,
    });
  }, []);

  return (
    <AuthModal width={480}>
      <>
        <LoginModalHeader errorMessage={errorMessage} />
        <LoginModalBody refs={refs} />
        <LoginModalFooter onLogin={onLogin} />
      </>
    </AuthModal>
  );
};

export default LoginPage;

 

최적화 적용한 코드

// LoginPage.tsx
import LoginModalBody from "@components/molecules/Div/LoginModalBody";
import LoginModalFooter from "@components/molecules/Div/LoginModalFooter";
import LoginModalHeader from "@components/molecules/Div/LoginModalHeader";
import AuthModal from "@components/organisms/Modal/AuthModal";
import { useRef, useState } from "react";

const LoginPage = () => {
  const emailRef = useRef<HTMLInputElement>(null);
  const passwordRef = useRef<HTMLInputElement>(null);
  const refs = { emailRef, passwordRef };

  const [errorMessage, setErrorMessage] = useState("");

  return (
    <AuthModal width={480}>
      <>
        <LoginModalHeader errorMessage={errorMessage} />
        <LoginModalBody refs={refs} />
        <LoginModalFooter refs={refs} setErrorMessage={setErrorMessage} />
      </>
    </AuthModal>
  );
};

export default LoginPage;
// LoginModalFooter.tsx
import DefaultButton from "@components/atoms/Button/DefaultButton";
import LinkText from "@components/atoms/Text/LinkText";
import Text from "@components/atoms/Text/Text";
import useLogin from "@hooks/query/useLogin";
import validateEmail from "@utils/validateEmail";
import { Dispatch, Ref, SetStateAction, useCallback } from "react";
import { useNavigate } from "react-router-dom";

interface LoginModalFooterProps {
  refs: any;
  setErrorMessage: Dispatch<SetStateAction<string>>;
}

const LoginModalFooter = ({ refs, setErrorMessage }: LoginModalFooterProps) => {
  const navigate = useNavigate();
  const { mutate: login } = useLogin(setErrorMessage);
  const { emailRef, passwordRef } = refs;

  const goRegisterPage = useCallback(() => navigate("/register"), []);

  const onLogin = useCallback(() => {
    if (!emailRef.current.value || !passwordRef.current.value) {
      return setErrorMessage("모든 값을 입력해주세요.");
    }
    if (!validateEmail(emailRef.current.value)) {
      return setErrorMessage("유효하지 않은 아이디입니다.");
    }
    if (passwordRef.current.value.length < 8) {
      return setErrorMessage("비밀번호는 8자리 이상이어야 합니다.");
    }
    setErrorMessage("");
    login({
      email: emailRef.current.value,
      password: passwordRef.current.value,
    });
  }, []);

  return (
    <>
      <DefaultButton text="로그인" onClick={onLogin} height={44} mb={12} />
      <Text color="auth-desc" fontSize="sm">
        계정이 필요한가요?
        <LinkText text=" 가입하기" onClick={goRegisterPage} />
      </Text>
    </>
  );
};

export default LoginModalFooter;

 

결과

로그인 버튼을 누르면 footer 부분만 리렌더링이 발생한다.

반응형