[TS]Formik 사용법

peppermint100
13 min readJul 20, 2020

서론

프론트엔드 개발을 하다보면 폼을 작성할 일이 많습니다. 일반적으로 로그인, 회원가입부터 신청 양식 등 필수적으로 폼을 작성해야 할일이 생깁니다.

아래는 일반적인 폼 작성 방식입니다.

<form onSubmit={handleSubmit}><input value={state} handleChange={(e) => {setState(e.target.value)}} /><button type="submit">submit</button>
</form>

리액트에서 state 변화를 통해 input을 작성하고 handleSubmit 함수를 통해 폼의 값을 가지고 다른 액션을 취할 수 있습니다.

이 경우에는 하나의 폼만 있어서 금방 작성하지만 만약에 우리가 어떤 기업에 보낼 온라인 이력서를 작성한다고 하면 이력서에는 이름, 나이, 경력 뿐만 아니라 주소와 같은 개인정보, 자기 소개서와 같은 수 많은 input이 존재할 것이고 모든 정보를 저장하기 위해 수많은 useState와 handleChange를 작성하게 될 것입니다.

이러한 귀찮은 작업을 깔끔하게 해주기 위해 React에는 React Hook Form, Redux Form, Formik등 여러가지 라이브러리가 존재합니다. 이번에는 타입스크립트를 잘 지원하는 Formik에 대해 알아보도록 하겠습니다.

설치

yarn add formik @material-ui/core

이번에 사용할 formik과 간단한 스타일링을 할 material-ui도 다운받아줍니다.

기본 구조

formik의 가장 기본 형태는 아래와 같습니다.

import React from 'react';
import { Formik } from "formik"
import { TextField, Button } from "@material-ui/core";
//
function App() {
return (
<>
<Formik initialValues={{ username: "", password: "" }} onSubmit={(data, { setSubmitting }) => {
setSubmitting(true)
console.log(data)
setSubmitting(false)
}}>
{
({ values, handleChange, handleBlur, handleSubmit, isSubmitting }) => (
<form onSubmit={handleSubmit}>
<div>
<TextField name="username" value={values.username} onChange={handleChange} />
</div>
<div>
<TextField name="password" value={values.password} onChange={handleChange} />
</div>
<Button type="submit" disabled={isSubmitting}>Submit</Button>
</form>
)
}
</Formik>
</>
);
}
export default App;

<Formik> 컴포넌트안에

{ () => () }

위와 같이 함수를 하나 설정해주고

필요한 props와 파라미터들을 정해줍니다.

initialValue와 onSubmit은 Formik 태그안에 꼭 들어가야하는 props이며 각각 초기 값과 폼을 제출 시에 실행될 함수를 담아줍니다.

그리고 아래 화살표 함수의 파라미터에는 values, handleChange, handleBlur, handleSubmit이 있습니다. values는 initialValues를 타입으로 갖게되는 실제 폼에 들어갈 값입니다.

그리고 setSubmitting은 비동기 통신에 사용이 됩니다. Formik의 onSubmit을 확인해 보면

onSubmit={(data, { setSubmitting }) => {
setSubmitting(true)
// 비동기 통신
console.log(data)
setSubmitting(false)
}}

양식을 submitting 중인지 boolean값으로 설정할 수 있습니다. 그리고 아래 isSubmitting을 통해 현재 비동기 통신중인지 확인할 수 있습니다. 따라서

<Button type="submit" disabled={isSubmitting}>Submit</Button>

버튼 쪽에 보면 isSubmitting이 true일 때는 양식을 두 번 보내지 않도록 버튼을 disable시킵니다.

근데 만약 인풋 값이

<div>
<TextField name="username" value={values.username} onChange={handleChange} />
</div>
<div>
<TextField name="password" value={values.password} onChange={handleChange} />
</div>

지금은 두개를 사용하고 있지만 인풋이 많아진다면 각각 onSubmit과 value, onChange 그리고 onBlur등을 똑같이 입력해 주어야 합니다. 하지만 Formik에서는 이 props들을 자동으로 넣어주는 인풋이 존재합니다.

import { Formik, Field, Form} from "formik"

Form 과Field라는 컴포넌트를 formik에서 가져오고

<Form>
<div>
<Field name="username" as={TextField} />
</div>
<div>
<Field name="password" as={TextField} />
</div>
<Button type="submit" disabled={isSubmitting}>Submit</Button>
</Form>

form 부분을 위와 같이 바꿔줍니다. 그러면 Field의 name만으로 handleSubmit, value, handleChange, handleBlur가 전부 자동설정되고 as를 통해 어떤 스타일의 input을 사용할 건지 정해주면 됩니다.지금 까지 Formik의 사용에서 가장 잘 살펴보아야 할 부분은 input의 name이 initialValue의 key 값과 일치해야 한다는 점입니다.

다양한 Input

import { Checkbox } from "@material-ui/core"

material ui에서 checkbox를 가져오고

<Formik initialValues={{ username: "", password: "", isNoob: false }} onSubmit={(data, { setSubmitting }) => {
setSubmitting(true)
console.log(data)
setSubmitting(false)
}}>

Formik의 initialValues에 isNoob이라는 boolean 인풋을 하나 추가해줍니다. 그리고 아래 Form에는

<div>
<Field name="isNoob" as={Checkbox} />
</div>

위와 같이 Checkbox를 추가해줍니다.

그리고 initialValues에

{{ username: "", password: "", isNoob: false, skills: [] }}

skills라는 빈 배열을 설정하고

<div>
<Field name="skills" as={Checkbox} value="React" />
<Field name="skills" as={Checkbox} value="Redux" />
<Field name="skills" as={Checkbox} value="Webpack" />
<Field name="skills" as={Checkbox} value="Typescript" />
</div>

위와 같이 value에 각각 다른 값을 주어 체크하면 skills에 값이 추가되도록 할 수 도 있습니다.

<div>
<Field name="nation" value="Korea" type="radio" />
<Field name="nation" value="Japan" type="radio" />
<Field name="nation" value="China" type="radio" />
</div>

또 nation이라는 빈 문자열을 initialValues에 추가하고 radio 버튼을 만들수도 있습니다.

useField

radio버튼에는 label이 필수적으로 붙어야 합니다. 이 체크 버튼이 어떤 값을 의미하는지 알려야하기 때문이죠.

import React from 'react';
import { Formik, Field, Form, useField, FieldAttributes } from "formik"
import { TextField, Button, Checkbox, Radio, FormControlLabel } from "@material-ui/core";
type RadioProps = { label: string } & FieldAttributes<{}>;const LabeledRadio: React.FC<RadioProps> = ({ label, ...rest }) => {
const [field,] = useField(rest);
return (
<FormControlLabel control={<Radio />} label={label} {...field} />
)
}

위와 같이 커스텀 라디오 버튼을 만들어줍니다.

<FormControlLabel control={<Radio />} label={label} {...field} />

위 컴포넌트는 metarial ui의 label이 붙은 라디오버튼입니다. useField를 통해 field에 들어가는 모든 values, handleChange등을 가져오고 FormControlLabel에 {…field}를 통해 뿌려줄 수 있습니다.

<div>
<LabeledRadio name="nation" value="Korea" label="Korea" type="radio" />
<LabeledRadio name="nation" value="Japan" label="Japan" type="radio" />
<LabeledRadio name="nation" value="China" label="China" type="radio" />
</div>

그리고 라디오 버튼 부분을 위와 같이 바꿔주면 각 라디오버튼에 레이블이 붙은 것을 확인할 수 있습니다.

Formik을 사용하니

위와 같이 아주 깔끔하게 코드가 작성됩니다. 만약 위와 같은 기능을 하는 코드를 useState를 통해서 작성을 하려면 코드가 굉장히 지저분해지고 만약 이후에 value 값 하나를 수정한다면 코드를 하나하나 뒤져가며 바꿔야 할 값을 찾는 불상사가 생길 것입니다. 하지만 Formik을 이용하면 values와 name만 바꿔주면 간단하게 handleChange, handleSubmit 모두가 한 번에 바뀌게 됩니다.

meta를 활용한 에러 메시지

const MyTextField: React.FC<FieldAttributes<{}>> = ({
placeholder,
...rest
}) => {
const [field, meta] = useField<{}>(rest);
const errorText = meta.error && meta.touched ? meta.error : "";
return (
<TextField
placeholder={placeholder}
{...field}
helperText={errorText}
error={!!errorText}
/>
);
};

위와 같은 컴포넌트를 작성해줍니다. useField는 Field 컴포넌트에 들어갈 handleChange 등을 받아오는 것 뿐만이 아니라 meta를 통해 input의 validation(검증)을 할 수 있습니다.

만약 meta를 통해 에러를 발견하면 helperText(metarial-ui에서 에러 메시지를 표시하는 prop입니다.)에 넣어서 에러를 표시할 수 있도록 해줍니다. 그리고 error의 유무는 errorText를 boolean 값으로 바꾸어서 확인할 수 있도록 해줍니다.

<Formik initialValues={{ username: "", password: "", isNoob: false, skills: [], nation: '' }}
validate={values => {
const errors: Record<string, string> = {}
if (values.username.includes("!") || values.username.includes("?")) {
errors.username = "! or ? cannot be username"
}
return errors;
}

} onSubmit={(data, { setSubmitting }
) => {
setSubmitting(true)
console.log(data)
setSubmitting(false)
}}>

그리고 Formik에 validate라는 함수를 추가해줍니다. values 중에 username이 “!” 또는 “?”를 가지고 있으면 errors.username에 메시지를 추가하고 리턴합니다. 그러면 …field에 errors가 전달이 되고

<MyTextField name="username" />

username을 담당하던 Field를 위와 같이 바꾸고 ?나 !를 인풋에 포함시켜서 submit을 하면 material-ui의 helperText가 에러메시지를 뿜어냅니다.

결론

다른 리액트 폼 라이브러리를 사용해 본적은 없지만 Formik은 굉장히 깔끔하고 유연하다고 느꼈습니다. 사실 폼 관리에 있어서 크게 신경쓰지는 않았었는데 리액트 개발자 로드맵을 보니 폼 관리 라이브러리가 있길래 들어만 보았던 Formik을 사용해 보았습니다. onChange와 state로 지저분하게 폼을 관리해 왔지만 Formik을 통하여 깔끔한 코드를 작성할 수 있게 되었고 이 후 폼을 유지보수하기도 쉬워지는 것을 볼 수 있었습니다.

--

--

peppermint100

기억하기 위해 또는 잊어버리기 위해 작성하는 블로그입니다.