이번 포스팅에서는 react에서 form의 상태관리를 도와주는 formik에 대하여 정리해본다.
웹 어플리케이션을 개발하다 보면 input, select, checkbox 등 다양한 입력으로 이뤄진 form을 자주 다루게 된다. 예를 들어 아래와 같은 room reservation form을 만들어야 한다고 가정해보자.
위 form에는 Name, Email, Room Type등 다수의 input field가 존재한다. 만약 순수 React만을 사용한다면, 위 form 내부의 모든 input에 state 변화를 직접 처리해주어야 한다.
사용자에게 받아야 하는 정보가 다양해 질수록(input field의 개수가 늘어 날수록) form 내부 state의 관리는 점점 더 어려워지고 복잡해진다.
다행히도 이런 form 내부 상태관리를 도와주는 여러 라이브러리들이 존재한다. 대중적으로 널리 사용되는 라이브러리에는 react-hook-form, formik, final-form 정도가 있다.
오늘은 이 중 가장 많이 사용되는 formik에 대해 살펴보자.
formik 공식 docs에 들어가 보면 아래와 같은 문구로 해당 라이브러리를 소개하고 있다.
Build forms in React, without the tears 🤣
formik을 이용하면 눈물 없이 form을 빌드할 수 있다고 한다. (사실 포믹 공부하며 더 많이 흘림..)
공식 문서에 따르면 formik은 폼에서 가장 성가신 3가지 부분을 쉽게 처리하게끔 도와준다.
- form state 값 가져오기
- validation을 및 에러 메시지 출력
- form submit 핸들링
포믹 공식 문서의 튜토리얼을 따라 진행해보며 formik의 위력을 느껴보자.
튜토리얼에서는 아래와 같은 간단한 form을 만들어 볼 것이다.
우선 간단한 하나의 input으로 시작해보자.
formik의 가장 기본적인 사용 형태는 아래와 같이 useFormik이라는 커스텀 훅을 이용하는 방식이다.
💡 useFormik이란?
formik의 가장 기본적인 이용 방식으로, hook 호출을 통해 form과 input의 상태관리에 필요한 여러가지 도구들을 담은 formik 객체를 리턴받아 form 내부 input에 대한 상태 관리를 진행할 수 있다.
useFormik 훅을 이용할 때는 initialValue와 onSubmit 함수를 전달하여야 한다. initialValue는 input의 초기 value이며, onSubmit은 form 제출 시 실행 되는 콜백 함수이다.
해당 함수들을 전달하여 useFormik 훅을 호출하면 formik 객체를 반환 받을 수 있다. formik 객체 안에는 form을 제어할 수 있는 여러가지 도구들이 담겨 있는데, 공식 문서에서는 이를 goodie bag(좋은 가방)이라고 표현하고 있다.
좋은 가방 내부를 조금 더 들여다보면, input state로 사용할 수 있는 value와 여러가지 handler (handleChange, handleBlur등)들이 담겨 있다. 따라서 이를 이용하면 별도의 추가 state 선언 없이 form 내부 상태 관리를 할 수 있다.
위 코드를 예시로 보면, formik 객체 안의 formik.handleChange와 formik.values.email을 이용해 email input field를 관리하고, formik.handleSubmit을 통해 form의 submit event를 관리하고 있다.
그런데 사실 위 예시 같이 input field가 하나인 경우, formik을 적용하는게 큰 이점을 가져다 준다고 보긴 어렵다. 따라서 이제 조금 더 일반적인 form의 상황을 가정해보자.
firstName, LastName, EmailAddress 3가지의 필드를 입력받는 form으로 바뀌었다. 위 코드를 보면, 하나의 formik 객체로 3개의 input을 모두 관리하고 있다.
formik은 input의 name property를 통해 어떤 input에서 변화가 일어났는지를 감지할 수 있다. 실제로 formik.values를 console로 찍어보면 우리가 전달한 name property가 state에 추가된 객체 형태로 출력된다.
따라서 formik 하나만으로 여러개의 input 상태에 대한 제어가 가능하다. 이렇게 여러개의 input이 추가되더라도 formik 하나로 form 전체를 관리할 수 있는 것이 formik의 장점이다.
formik을 사용하지 않고 실제로 handleChange 함수를 구현하기 위해서는 아래와 같은 코드를 작성해야만 한다.
사실 위 코드를 작성하는 일은 다소 귀찮지만 그렇게 어려운 일은 아니므로, formik을 도입할만한 이유까지 되진 않는 것 같다.
맞다! formik은 상태 관리 뿐만 아니라 form 관리에 필요한 다양한 기능을 함께 제공하고, 이 기능들을 조합해서 사용했을때 더 강력하다. 조금 더 살펴보자.
formik은 state 관리 외에 input의 validation 기능도 함께 제공한다.
validation은 2가지 방식으로 진행할 수 있다. 첫번째 방식은 useFormik hook에 validate property에 객체를 직접 정의하는 방식이고, 두번째 방식은 yup을 이용하여 진행하는 방식이다.
두가지 방식 중 Formik 공식문서에서는 yup을 이용한 방식을 권장하고 있다. 적용 방식은 굉장히 간단한데, useFormik에 값을 넘길때 Yup의 object method를 이용해서 정의한 validation object를 함께 넘겨주기만 하면된다.
위에서 작성한 코드에 validation을 추가로 적용해보자.
각 필드에 대한 validation 로직을 적용한 Yup object를 함께 넘겨주었다.
그러면 formik이 자동으로 input에 대한 validation을 진행해준다. LastName과 Email Address 필드에 유효하지 않은 값을 입력했을 경우, 에러 메세지를 띄워주는 것을 확인할 수 있다.
이제 formik에 대한 기본 내용은 다 살펴 보았다. 추가적으로 formik에서 제공하는 여러가지 API들을 이용하여 코드를 조금 더 개선해보자.
지금 까지의 코드를 보면 모든 input에 onChange, value, onBlur 값이 동일하게 전달되고 있다.
formik은 이런 중복을 제거할 수 있도록 getFieldProps 메서드를 제공한다.
getFieldProps 메서드는 input, select, textarea 태그에 적용할 수 있으며 주어진 필드에 맞춰 onChange, onBlur, value, checked를 리턴 한다. 위 코드에 getFieldProps를 적용하면 아래과 같이 개선이 가능하다.
훨씬 깔끔하다! getFieldProps를 전체 코드에 적용해 보자.
getFieldProps를 이용하여 많은 중복을 제거할 수 있었지만, 결국 input 태그 별로 해당 props를 수동으로 넘겨줘야 하는 것은 변하지 않았다.
Formik은 사용자들이 코드를 조금 더 효율적으로 작성할 수 있도록 context API 기반의 추상화된 컴포넌트들을 제공한다.
해당 컴포넌트들은 contextAPI를 통해 컴포넌트 내부에서 필요한 props(onChange, onBlur 등)에 바로 접근할 수 있기 때문에 getFieldProps를 전달하는 과정 또한 생략할 수 있다.
추상화된 컴포넌트로는 <Formik\>
, <Field\>
, <Form\>
, <ErrorMessage\>
컴포넌트 등이 있다.
<Formik\>
컴포넌트를 사용하면 useformik hook을 대체 할 수 있다.
<Formik\>
컴포넌트는 내부적으로 useFormik을 호출하고 이를 children 컴포넌트에게 formik 객체를 전달한다. 따라서 해당 컴포넌트로 우리의 컴포넌트를 감싸기만 하면 내부에 있는 컴포넌트들은 모두 formik 객체를 사용 가능하다.
formik 공식 문서에서 제공하는 Formik 컴포넌트의 의사 코드(pseudocode)는 아래와 같다. 코드를 보면 <Formik\>
컴포넌트 내부에서 useFormik 훅을 호출하여 contextAPI를 이용해 내부로 전달하고 있다.
궁금해서 실제로 formik 레포지터리에 들어가 보았는데, 아래 이미지와 같이 Formik 컴포넌트에 내부적으로 useFormik을 호출해서 사용하는 것을 확인할 수 있었다.
이 외에도 input을 대체하는 Field
, form을 대체하는 Form
, error를 대체하는 ErrorMessage
컴포넌트도 제공한다.
Field
컴포넌트만 살펴보자. Field
컴포넌트는 는 디폴트로 input 태그로 render 되지만, as
prop을 이용하여 textarea 혹은 select로도 사용할 수 있다. styled-component의 문법과 비슷하다.
Field
컴포넌트에 대한 자세한 API 스펙은 여기서 확인할 수 있다.
해당 컴포넌트들을 적용하기 전에, 현재까지의 코드를 다시 살펴보자.
이제 위 코드에 최종적으로 Form, Field, Error 컴포넌트를 적용해보자.
폼의 state와 input의 validation을 하나하나 관리하는 초기 코드에 비해, 처음에 비해 코드가 훨씬 간결해 진 것을 확인할 수 있다.
이렇게 튜토리얼을 따라 일반적인 폼에서부터 formik을 적용한 폼으로 변경해 보았다! 상태 관리부터 필드 유효성 체크, submit 관리까지 알아서 해주니 작성해야할 코드를 꽤 많이 줄일 수 있다는 생각이 든다.
이대로 끝내기는 아쉬우니 마지막으로 한가지만 더 살펴보자. 결과적으로는 Formik 소개글이 되었지만 사실 처음에는 해당 고민을 해결하는 과정을 공유하고 싶어 이 글을 쓰게 되었다.
만약 네이티브 input 태그가 아닌, 커스텀 된 컴포넌트 혹은 기타 UI 라이브러리(MUI, Ant Design등)를 이용하여 formik을 이용하고 싶은 경우는 어떻게 해야 할까?
예를 들어 아래 이미지의 <CustomInput>
컴포넌트에 formik을 적용하고 싶다고 가정해보자.
우선 일반 formik을 적용할 때와 같이Field에서 props로 넘어오는 onChange 함수를 <CustomInput>
컴포넌트에 전달해주자.
그리고 커스텀 컴포넌트에서 onChange시 특정한 로직을 진행해야 하는 경우에는, 전달 받은 field.onChange 함수를 먼저 호출해 준 뒤에 추가 로직을 수행하는 방식으로 진행할 수 있다.
form 관리에 사용하기 위해 formik에 대해서 알아보았는데, 생각보다 이해하는데 시간이 조금 걸렸다. 간단하지는 않지만 잘 익혀두면 강력한 도구가 될 것 같다.
깃 레포 이슈들을 살펴보니 관리해야하는 폼 내부 element들이 많아지면 성능 이슈가 조금 있는 듯 하다🤔
시간 날때 조금 더 살펴봐야겠다.