Explore the limits of mobile web experience
회사에서 신규 프로젝트에 antd-mobile을 사용하기로 했다. 페이지 소개에 적힌 문구를 보면 자신감이 넘치는데 광범위하게 제공하는 컴포넌트를 보면 그럴만하다는 생각이 든다. 다만 아직 사용해본 지 며칠 지나지 않았으므로 앞으로 여러 가지 문제를 맞닥뜨리게 될지도 모르겠다.
이 글에서는 입력 폼을 구현하던 도중 부딪힌 어려움에 대해 다룬다. gist 하나 저장해두면 끝인 간단한 문제지만 혹시 같은 고통을 겪는 분들에게 도움이 되기를 바라며 블로그로 공유해본다.
High-performance form controls with built-in data field management. Including data entry, verification and corresponding styles.
antd-mobile에서는 자체적으로 Form의 필드를 관리하는 유용한 기능을 제공한다. Form.useForm()으로 생성한 FormInstance 객체를 받아 컨트롤을 돕는 form props가 있고 그 밖에도 onFieldsChange, onValuesChange, onFinish, onFinishFailed props 등을 사용해 다양한 이벤트를 관리할 수 있게 도와준다.
그러나 antd-mobile과 react-hook-form의 상성이 좋은지는 의문이다. 회사의 다른 서비스에서는 모두 이 라이브러리를 사용하고 있었고 mui와 잘 어울리므로 antd에도 쉽게 적용할 수 있을 거라 생각했다. 그런 생각에 가벼운 마음으로 도전했으나 value와 onChange 이벤트 핸들링을 거쳐 에러 메시지를 표시하는 부분에서 난관에 빠졌다.
이리저리 구글링해보고 깃허브 이슈에서 검색해보아도 이전 버전에 대한 해결 방법만 찾을 수 있었다. 약 반나절을 소모한 뒤 antd-mobile의 버전을 내리기보다는 react-hook-form을 배제하고 antd-mobile만 순수하게 사용하기로 했다.
📦 codesandbox에서 실습이 가능합니다.
문서를 참고해 Form.item의 rules로 에러 메시지를 쉽게 표현할 수 있었다. 그런데 react-hook-form처럼 버튼을 비활성화하려고 하니 isDirty나 isValid와 같은 form state를 따로 제공하지 않는 게 아닌가! 다행히 직접 상태를 관리함으로써 해결할 수 있어 실습 코드와 설명을 남긴다.
일단 validation을 추가한 간단한 Form을 살펴보자. rules에 필드가 required여야 한다는 간단한 조건을 추가했다.
export default function SampleForm() {
return (
<Form
footer={
<Button block type="submit" color="primary">
Submit
</Button>
}
>
<Form.Item
name="name"
label="Name"
rules={[{ required: true, message: "Please input your name" }]}
>
<Input placeholder="Enter your name" />
</Form.Item>
</Form>
);
}
먼저 form을 컨트롤하기 위해 Form.useForm()으로 FormInstance를 생성한다.
이어서 isDirty와 isValid를 각각 state로 선언하고 둘 중 하나라도 false라면 버튼이 비활성화되도록 조건을 추가한다.
Form의 onFieldsChange 이벤트가 발생할 때 form.isFieldsTouched()에 따라 isDirty를, form.getFieldsError()의 에러 개수에 따라 isValid를 업데이트하면 된다.
export default function SampleForm() {
const [form] = Form.useForm();
const [isDirty, setIsDirty] = useState(false);
const [isValid, setIsValid] = useState(false);
return (
<Form
form={form}
footer={
<Button
block
type="submit"
color="primary"
disabled={!isDirty || !isValid} // isDirty와 isValid를 사용해 값이 입력되지 않았거나 입력된 값이 유효하지 않으면 버튼을 비활성화
>
Submit
</Button>
}
onFieldsChange={() => {
setIsDirty(form.isFieldsTouched()); // 필드가 하나라도 터치(작동)되었다면 form에 변화가 있다고 판단
const hasError = form
.getFieldsError()
.some(({ errors }) => errors.length); // 필드별 에러 메시지가 하나라도 존재하는지 확인
setIsValid(!hasError); // 에러 메시지가 하나도 없으면 form이 유효하다고 판단
}}
>
<Form.Item
name="name"
label="Name"
rules={[{ required: true, message: "Please input your name" }]}
>
<Input placeholder="Enter your name" />
</Form.Item>
</Form>
);
}
아래 이미지처럼 에러 메시지와 버튼 활성화 상태가 자연스럽게 변경되는 것을 확인할 수 있다.
잘못된 부분에 대한 지적은 언제든 환영합니다! 😉