React 랜더링 다루기

김봉섭·2023년 5월 13일
1

TieDesign 프로젝트

목록 보기
4/6

목적: 쿼리스트링을 이용해 param의 값에 따라 컴포넌트 return 하기

전체 코드

interface IParams {
  styleMode: string;
}

const CustomTie = ({ params }: { params: IParams }) => {
  const [tieColor, setTieColor] = useState("black");
  const [mode, setMode] = useState("one point");
  const [isLoading, setIsLoading] = useState(false);

  const {
    register,
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm<FieldValues>({
    defaultValues: {
      text: "",
      image: "",
    },
  });

  const image = watch("image");

  const OptionBody = useCallback(() => {
    // TODO:왜 hi가 계속 실행 되는겨
    console.log("hi");
    switch (params.styleMode) {
      case "pointLogo":
        return <FileInput id="image" register={register} errors={errors} disabled={isLoading} />;
      case "textLogo":
        return <Input id="text" label="텍스트" register={register} errors={errors} disabled={isLoading} />;
      case "patternAllOver":
        return <div>pattern all over</div>;
      case "onePattern":
        return <div>one pattern</div>;
      case "twoPattern":
        return <div>two pattern</div>;
      case "fourPattern":
        return <div>four pattern</div>;
      default:
        return <div></div>;
    }
  }, []);

  return (
    <ClientOnly>
      <Container>
        <div className="flex pt-10">
          <div className="bg-neutral-100 w-full rounded relative flex justify-center">
            <div className="absolute">
              {image[0] && <Image src={URL.createObjectURL(image[0])} alt="imagePreview" width={48} height={48} />}
            </div>
            <div className="p-20">
              <Tie width="160" hegith="160" fill={tieColor} />
            </div>
            <Image src="/images/tieShadow.png" width="400" height="160" alt="tie" className="absolute top-2" />
          </div>
          <div className="w-full pl-10 flex flex-col gap-8">
            <ColorList setTieColor={setTieColor} />
            <OptionBody />
            <Button label="견적 요청 하기" onClick={() => {}} />
          </div>
        </div>
      </Container>
    </ClientOnly>
  );
};

export default CustomTie;

기존 부분 코드

 const OptionBody = () => {
    switch (params.styleMode) {
      case "pointLogo":
        return <FileInput id="image" register={register} errors={errors} disabled={isLoading} />;
      case "textLogo":
        return <Input id="text" label="텍스트" register={register} errors={errors} disabled={isLoading} />;
      case "patternAllOver":
        return <div>pattern all over</div>;
      case "onePattern":
        return <div>one pattern</div>;
      case "twoPattern":
        return <div>two pattern</div>;
      case "fourPattern":
        return <div>four pattern</div>;
      default:
        return <div></div>;
    }
 }

문제점 : OptionRender 컴포넌트가 리랜더링되기 때문에 fileInput의 네임이 초기화되어 파일이름이 화면에 그려지지 않는다.

첫번 째 해결 방법: useCallback 사용하기

변경된 코드

 const OptionBody = useCallback(() => {
    switch (params.styleMode) {
      case "pointLogo":
        return <FileInput id="image" register={register} errors={errors} disabled={isLoading} />;
      case "textLogo":
        return <Input id="text" label="텍스트" register={register} errors={errors} disabled={isLoading} />;
      case "patternAllOver":
        return <div>pattern all over</div>;
      case "onePattern":
        return <div>one pattern</div>;
      case "twoPattern":
        return <div>two pattern</div>;
      case "fourPattern":
        return <div>four pattern</div>;
      default:
        return <div></div>;
    }
  }, []);

OptionRender 컴포넌트를 useCallback을 사용해줬다.

문제점 : useCallback은 register, errors, isLoading은 state이다. 따라서 해당 값이 바뀌면 OptionRender가 재 랜더링 되어야 하는데 useCallback을 사용해서 최신화를 보장하지 못하게 된다.

생각한 부분 :위 방식은 OptionRender 컴포넌트를 따로 tsx파일로 분리하지 않고 CustomTie라는 하나의 tsx에서 2개의 컴포넌트를 만들어서 사용했다. 덕분에 CustomTie 내부에서 사용된 state를 인자로 전달 받지 않고도 사용이 가능했고 그 결과 리랜더링시 OptionRender 컴포넌트가 state변경마다 리랜더링 되었다.

해결 방법 : 컴포넌트를 분리하여 인자로 state 전달하기

interface IParams {
  styleMode: string;
}

const CustomTie = ({ params }: { params: IParams }) => {
  const [tieColor, setTieColor] = useState("black");
  const [mode, setMode] = useState("one point");
  const [isLoading, setIsLoading] = useState(false);

  const {
    register,
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm<FieldValues>({
    defaultValues: {
      text: "",
      image: "",
    },
  });

  const image = watch("image");
  console.log(image[0]?.name);

  return (
    <ClientOnly>
      <Container>
        <div className="flex pt-10">
          <div className="bg-neutral-100 w-full rounded relative flex justify-center">
            <div className="absolute">
              {image[0] && <Image src={URL.createObjectURL(image[0])} alt="imagePreview" width={48} height={48} />}
            </div>
            <div className="p-20">
              <Tie width="160" hegith="160" fill={tieColor} />
            </div>
            <Image src="/images/tieShadow.png" width="400" height="160" alt="tie" className="absolute top-2" />
          </div>
          <div className="w-full pl-10 flex flex-col gap-8">
            <ColorList setTieColor={setTieColor} />
            <OptionBody styleMode={params.styleMode} register={register} errors={errors} isLoading={isLoading} />
            <Button label="견적 요청 하기" onClick={() => {}} />
          </div>
        </div>
      </Container>
    </ClientOnly>
  );
};

export default CustomTie;
interface OptionBodyProps {
  styleMode: string;
  register: UseFormRegister<FieldValues>;
  errors: FieldErrors;
  isLoading?: boolean;
}

const OptionBody: React.FC<OptionBodyProps> = ({ styleMode, register, errors, isLoading }) => {
  switch (styleMode) {
    case "pointLogo":
      return <FileInput id="image" register={register} errors={errors} disabled={isLoading} />;
    case "textLogo":
      return <Input id="text" label="텍스트" register={register} errors={errors} disabled={isLoading} />;
    case "patternAllOver":
      return <div>pattern all over</div>;
    case "onePattern":
      return <div>one pattern</div>;
    case "twoPattern":
      return <div>two pattern</div>;
    case "fourPattern":
      return <div>four pattern</div>;
    default:
      return <div></div>;
  }
};

export default OptionBody;

컴포넌트를 분리하여 인자로 전달 해주니 원하던 대로 파일 인풋의 값이 변경되지 않았다.

state값이 변경되기 때문에 OptionBody 컴포넌트가 리랜더링은 되었지만 fileinput의 이름이 초기화 되지 않았다. 그 이유가 무엇일까?

profile
프론트엔드 웹&앱 개발

1개의 댓글

comment-user-thumbnail
2023년 9월 25일

이유 알려주세요 ㅠ

답글 달기