
프로젝트를 진행하면서, 이런 모양의 컴포넌트가 비슷하게 쓰이는 것 같은데 쓰는 사람들마다 다 다른 방식으로 사용을 하고 있다는 걸 알게 되었다.
물론 비슷하다고 다 공통으로 만들 필요는 없다는 건 알지만
const ProjectInputBox = ({
name,
label,
footer,
children,
width,
}: ProjectInputBoxProps) => {
const {
register,
formState: { errors },
} = useFormContext<ProjectFormValues>()
return (
<label htmlFor={name}>
<Text
fontSize="md"
as="b">
{label}
</Text>
</label>
<ErrorMessage
name={name}
errors={errors}
render={({ message }) => <ErrorText message={message} />}
/>
{isValidElement(children) &&
cloneElement(children as ReactElement<InputElementProps>, {
id: name,
...register(name, projectInputRegister[name]),
})}
{footer && (
<Text
fontSize="sm"
color="grey">
{footer}
</Text>
)}
)
}
컴포넌트를 분리해서 보면 3개로 나눌 수 있다
그래서 합성 컴포넌트 방식으로 컴포넌트를 3개의 컴포넌트로 나눈 다음, 각 컴포넌트가 독립적인 역할을 하도록 나누어 본다면 좋을 것 같다는 생각이 들었다
<InputBox>
<InputBox.Header/> - 최상단 담당
<InputBox.Input/> - 중간 담당
</InputBox>
그런데 뭔가 이것만으로는 좀 부족하다는 생각이 들었다.
그래서 조금 더 기능을 추가해보기로 했다
일단 제일 먼저 생각난 것은
label 태그의 htmlFor 와 input의 id 값이 동일해야 한다
- 무조건 동일해야 하는건 아니지만, 값이 동일할 경우 얻을 수 있는 장점이 많이 존재한다
물론 label과 input에 따로 props로 전달하는 것도 그리 어렵지 않은 방법이지만, 하나의 값으로 관리하게 된다면 human error를 방지할 수 있지 않을까? 라는 생각이 들었다
label 태그를 사용하는 게 좋다text 는 외부에서 자유롭게 커스텀이 가능해야 한다단순히 text만 넣을 수 있게끔 string 타입으로 제한해도 편할 것 같지만, 확장성을 생각하여 children 의 타입을 ReactNode로 했다
const InputHeader = ({ children }: InputHeaderProps) => {
const { id } = useInputBoxContext()
return (
<label htmlFor={id}>{children}</label>
)
}
이 부분에서 고민한 점이
일단 컴포넌트명이 Input인 만큼, 뭔가 Input에 대해서만 다룰 수 있으면 좋을 것 같다는 생각이 들었다.
const InputContent = forwardRef<HTMLInputElement, InputContentProps>(
({ children}, ref) => {
const { id } = useInputBoxContext()
return (
<div ref={ref}>
{cloneElement(children, {
id,
})}
</div>
)
},
)
cloneElement 를 통해 props에 id를 추가함으로써 label과 연결문제점 - id 라는 props가 오버라이딩 되지 않을까? 했지만, 결론은
useInputBoxContext() 의 id값으로 적용되어서 문제 없다
cloneElement함수에 전달된 id 값이 최종적으로 children에 설정됩니다.
Footer 와 사용하면서 생긴 수정사항은 다음으로..