오늘은 이번 주 동안 나를 힘들게했던....form 에 대해서 간단히 정리해보려 한다,,
일단 내가 구현해야했던 구조의 form 은 그림과 같이 추가되는 항목이 있는 한 세트의 폼이 또 추가되는 형식...?

전에 구현했던 페이지를 그대로 가져와서 디자인만 수정하면 된다고 생각했는데,
이전과 비즈니스 로직이 수정되면서 form 기능을 다시 구현해야 했다,,,
기존에는 파란색으로 표시한 것과 같이 타이틀, 타입, 콘텐츠가 하나의 폼이라 폼 작성 -> 추가 -> api 요청 -> 다른 폼 작성 ... 과 같은 과정이었다면,
바뀐 로직은 폼 작성 -> 추가 -> 작성 -> 추가 ... -> 한꺼번에 api 요청 이렇게 되면서 form 기능을 처음부터 구현해야했다...ㅠㅠ
그럼 문제가 뭐였는지, 이 기능을 어떻게 구현했는지 살펴보자! (참고로 현재 프로젝트에서는 formik 과 yup 이라는 라이브러리를 이용하여 폼을 구현하고 있다! )
문제
- 원래 구현하던 방식은 전체를 폼으로 감싸고, 추가되는 하나의 항목에 fieldarray 를 적용하는 방식
- 하지만 이제 전체 폼도 여러 번 추가되어야 하기때문에, 더 큰 범위로 묶은 후 전체 폼을 하나의 항목처럼 fieldarray 로 묶어 추가되도록 해주어야 함
- 이 경우 yup 을 이용한 validation 도 다시 정의해주어야 함
- +) error 메세지 구현 시 사용된 touch 가 제대로 작동하지 않아 submit 로직을 새로 구현해야 함
여기서 말한 formArray 는, 배열처럼 같은 형식의 데이터가 추가되거나 삭제될 수 있도록 formik 에서 제공하는 기능이다!
사용된 개념
- formik
- values, setfieldvalue, errors, validateForm, isValid ...
- fieldarray
- remove, push
- yup
해결
- 하나의 폼을 배열로 묶은 객체를 새로 만들어 initial value 로 설정
- values 에 questions 라는 키값 안에 배열로 폼들의 값이 옴
// BEFORE
const initQuestion = {
// id: '',
question: '',
questionType: 'SINGLE_CHOICE' as 'SINGLE_CHOICE' | 'MULTIPLE_CHOICE' | 'SHORT_FORM',
required: true,
answerList: ['', ''],
// hasEtc: false,
};
// AFTER
const dummyQuestion: dummyQuestionType = {
questions: [initQuestion],
};
// BEFORE - values
{
question: '',
questionType: 'SINGLE_CHOICE',
required: true,
answerList: ['', '']
}
// AFTER - values
{
questions: [
{
question: ''.
questionType: 'SNIGLE_CHOICE',
required: true,
answerList: ['','']
},
{
// ...
},
]
}
- 하나의 폼을 보여주는 코드를 분리해 컴포넌트화 시키고, 해당 컴포넌트를 fieldarray 를 이용해 반복시키는 동시에 각 컴포넌트 안에서도 fieldarray 를 사용해줌
- 이때, 반복되는 field 들은 이름을 설정할 때 index 를 활용해주어야 값이 구분되어 설정된다
- name 이 구분되면 특정 위치의 데이터에 바른 값이 들어가고, push, remove 와 같은 올바르게 기능도 작동함
- push, remove 와 같은 내장 기능을 통해 항목 추가와 삭제를 쉽게 구현할 수 있다
// 전체 폼
<Formik
initialValues={Number(typeid) ? _dummyResult : dummyQuestion} // 수정이면 불러온 데이터를 initial value 로, 생성이면 initValue 를 넣음
onSubmit={onSubmit}
validationSchema={validationSchema}
// ...
>
{({ values, setFieldValue, validateForm, errors, isValid }) => (
<>
<Form>
<FieldArray name="questions">
{({ remove, push, form }) => (
<>
<div>
{values.questions.map((f, index) => {
return (
<div key={index}>
<FormCard
// ...
/>
</div>
);
})}
</div>
{/* ... */}
</>
)}
</FieldArray>
</Form>
{/* ... */}
</>
)}
</Formik>
// 컴포넌트
<div css={formStyle}>
<div className="ds-flex fd-c gap-48 mb-48">
<section>
<Label type="body1" bold="medium" color={palette.gray.gray6} isRequired>
질문 제목
</Label>
<div
css={css`
div {
width: 552px;
}
`}
>
<Input
name={`questions.${index}.question`}
value={question}
onChange={(e) => setFieldValue(`questions.${index}.question`, e)}
placeholder="질문을 입력해주세요,"
style={{ height: '37px' }}
/>
{errorMsgVisible && (
<p className="err">{errors && errors[index] && errors[index]?.question}</p>
)}
</div>
</section>
{/* ... */}
<section>
<FieldArray name={`questions.${index}.answerList`}>
{({ remove, push }) => (
<div>
<section className="answer-list-header">
{/* ...개별 폼에서 추가되는 항목 */}
</section>
</div>
)}
</FieldArray>
</section>
</div>
</div>
- validation 을 위한 yup 은 initial value 형태와 동일하도록 array(), object() 를 활용하여 정의해준다
const validationSchema = yup.object().shape({
questions: yup.array().of(
yup.object().shape({
question: yup.string().required('질문 제목을 입력해주세요.'),
// questionType: yup.string().required('질문 유형을 선택해주세요.'),
answerList: yup.array().when('questionType', {
is: (type: string) => type !== 'SHORT_FORM',
then: yup
.array()
.min(2, '항목을 2개 이상 입력해주세요.')
.test({
name: 'is-blank',
message: '항목을 모두 입력해주세요.',
test: (value) => {
for (let i = 0; i < value?.length!; i++) {
if (!!value?.[i]) {
continue;
} else {
return false;
}
}
return true;
},
}),
}),
}),
),
});
이 때 참고한 자료는 아래에!
[React] react-hook-form과 yup으로 동적 배열 유효성 검증하기
안녕하세요. J4J입니다. 이번 포스팅은 react-hook-form과 yup을 이용하여 동적 배열 유효성 검증하는 방법에 대해 적어보는 시간을 가져보려고 합니다. 기본적인 방법 긴말 없이 바로 코드부터 보도
jforj.tistory.com
https://www.npmjs.com/package/yup#api
yup
Dead simple Object schema validation. Latest version: 0.32.11, last published: a year ago. Start using yup in your project by running `npm i yup`. There are 4112 other projects in the npm registry using yup.
www.npmjs.com
- submit 시 최초 submit 이후부터 에러메세지가 출력되도록 구현.
- 최초 submit 구분하는 상태 추가
- 최초 상태에서 에러메세지가 출력되지 않아 빈 상태로 submit 이 가능하기 때문에 isValid 옵션을 이용하여 submit 버튼에 분기처리 해줌
<Button
type="solid"
size="large"
// htmlType="submit"
style={{ width: '240px' }}
onClick={() => {
validateForm();
!firstSubmitToggle && setFirstSubmitToggle(true);
if (isValid) return onSubmit(values);
}}
>
저장하기
</Button>
후딱 정리하려니까 되게 간단해보이네,,,
프로젝트 하면서 공부했던 부분이라 코드는 거의 다 생략해서 그런지 코드도 짧아졌다! 그래도 간단히 결과물을 조금 보여주면서
오늘 글도 마무리해본당! 안녕!
'Dev > react' 카테고리의 다른 글
| 웹 개발자로 입사해서 앱 개발까지 하고있는 썰 푼다.../리액트 네이티브 박치기 / 기본 문법부터 라이브러리 사용까지 (1) | 2023.11.02 |
|---|---|
| compound component pattern / 리액트 디자인 패턴 / 디자인 시스템의 길은 멀고도 험하구나 (0) | 2023.01.30 |
| 프론트엔드라면 디자인 시스템 하나 정도는 가지고 있어야 한다 카더라 / storybook 으로 디자인 시스템 개발 / npm 배포 (1) | 2023.01.08 |
| 자동 스크롤 문제 해결 / scrollTop = scrollHeight 적용 안됨 (1) | 2022.09.22 |
| onPressEnter 한글 중복 입력 이슈 해결 (0) | 2022.09.22 |