전체 코드
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의 이름이 초기화 되지 않았다. 그 이유가 무엇일까?
이유 알려주세요 ㅠ