본문 바로가기

Frontend/React

[React] Formik 라이브러리(Field,Formk, 버튼 선택)

728x90
이번 프로젝트 form 관리를 formik을 통해 하기로 결정돼
formik에 대한 공부 기록을 남기려고 한다.

 

form state 관리에 사용되는 라이브러리인 react-hook-form에 대해 기록한 적이 있다.

 

[React] react-hook-form 사용법(useForm, watch)

프로젝트 진행 중 로그인, 회원가입 기능을 구현하면서 react-hook-form이라는 편리한 API를 찾게 되어서 간단한 사용법을 기록하려고 한다. react-hook-form은 여러 개의 input 태그를 효율적으로 관리할

choi-records.tistory.com

 

하지만 지금 가장 많이 사용되는 라이브러리가 formik이기도 하고,

현재 진행 중인 프로젝트에서 사용되는 라이브러리이므로 formik을 공부해 보려고 한다.

개요

formik이 지원하는 가장 대표적인 3가지의 기능은 아래와 같다.

  1. Getting values in and out of form state
  2. Validation and error message
  3. Handling form submission

이번 프로젝트에서는 input 뿐만 아니라

select가 아닌 버튼으로 다른 입력값을 받아야 한다.

 

이번 기록에서는 formik의 기능을 둘러보며 프로젝트에서 이용하는 기능을 구현해 보려고 한다.

저장해야 하는 입력값은 아래와 같다.

이름, 전화번호, 이메일, 캠퍼스(자연/인문), 학년, 학적(재학/휴학)

캠퍼스와 학적은 둘 중 하나의 버튼을 클릭하여 결정한다.

 

이제 formik으로 위의 기능을 구현해 보자.

Formik

먼저 formik이 작동하는 기본적인 코드부터 보자.

<Formik
       initialValues={{ email: '', password: '' }}
       validate={values => {
         const errors = {};
         if (!values.email) {
           errors.email = 'Required';
         } else if (
           !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
         ) {
           errors.email = 'Invalid email address';
         }
         
         if(!values.password) {
         	errors.password='Required!';
         } else if (values.password.length > 10){
         	errors.password = 'Must be 10 characters or less';
            }
            
         return errors;
       }}
       onSubmit={(values, { setSubmitting }) => {
         setTimeout(() => {
           alert(JSON.stringify(values, null, 2));
           setSubmitting(false);
         }, 400);
       }}
     >
       {({
         values,
         errors,
         touched,
         handleChange,
         handleBlur,
         handleSubmit,
         isSubmitting,
         /* and other goodies */
       }) => (
         <form onSubmit={handleSubmit}>
           <input
             type="email"
             name="email"
             onChange={handleChange}
             onBlur={handleBlur}
             value={values.email}
           />
           {errors.email && touched.email && errors.email}
           <input
             type="password"
             name="password"
             onChange={handleChange}
             onBlur={handleBlur}
             value={values.password}
           />
           {errors.password && touched.password && errors.password}
           <button type="submit" disabled={isSubmitting}>
             Submit
           </button>
         </form>
       )}
     </Formik>

Formik이라는 컴포넌트에 props로 initialValues, validate, onSubmit을 넘겨준다.

initialValues는 각 state의 초깃값을 담고 있는 객체,

validate는 전체 데이터 객체를 prop로 전달받고, 유효성 검사를 진행하는 함수,

onSubmit은 전체 데이터 객체와 optional 객체를 전달받고, submit 이벤트에 대한 이벤트 핸들러다.

 

각 input태그에 대해 따로 지정을 해줘야 하는 것 같다.

useFormik

formik은 useFormik 훅을 지원한다.

useFormik은 위에서 지정한 formikConfig를 인자로 전달받고, form에서 필요한 정보들을 반환한다.

export interface FormikConfig<Values> extends FormikSharedConfig {
    component?: React.ComponentType<FormikProps<Values>> | React.ReactNode;
    render?: (props: FormikProps<Values>) => React.ReactNode;
    children?: ((props: FormikProps<Values>) => React.ReactNode) | React.ReactNode;
    initialValues: Values;
    initialStatus?: any;
    initialErrors?: FormikErrors<Values>;
    initialTouched?: FormikTouched<Values>;
    onReset?: (values: Values, formikHelpers: FormikHelpers<Values>) => void;
    onSubmit: (values: Values, formikHelpers: FormikHelpers<Values>) => void | Promise<any>;
    validationSchema?: any | (() => any);
    validate?: (values: Values) => void | object | Promise<FormikErrors<Values>>;
    innerRef?: React.Ref<FormikProps<Values>>;
}

config를 전달하는 것을 따로 분리하는 것이 확실히 깔끔할 것 같다.

Validation

위의 코드에서처럼 formik은 유효성 검사 및 오류 메세지 기능도 지원한다.

위 코드의 validate에 전달된 함수를 따로 분리해 useFormik의 config 설정에 전달하는 방법도 있다.

 

formik의 onSubmit 함수는 error가 없을 때 실행된다.

error 객체가 빈 객체일 때 작동한다.

 

formik에서 유효성 검사는 위처럼 validate에 함수를 전달해서 구현할 수도 있지만,

Yup 라이브러리를 사용할 수도 있다.

 

formik에서는 위의 fomikConfig의 인터페이스에서 볼 수 있듯

validationSchema 속성을 가지고 있는데

이 속성은 Yup의 유효성 검사 오류 메세지를 formik이 원하는 형태의 객체로 변환해 준다.

Field

form에는 우리가 구현하려는 버튼 등 input 외의 state도 존재할 수 있다.

formik은 field를 통해 이런 state도 전체 submit 값에 포함시킬 수 있도록 한다.

 

formik은 field를 통해 공유될 수 있는 재사용 가능한 컴포넌트를 구축할 수 있도록 한다.

field는 input 뿐 아니라 다른 선택 메뉴도 state로 존재할 수 있도록 해준다.

 

이제 필요한 기능을 구현해 보자.


기능 구현

먼저 formik 컴포넌트부터 보자.

Formik

      <Formik
        initialValues={{
          name: "",
          phoneNum: "",
          email: "",
          grade: "",
          academic: "",
          Campus: ""
        }}
        validationSchema={Yup.object({
          name: Yup.string()
            .max(15, "Must be 15 characters or less")
            .required("Required"),
          phoneNum: Yup.string()
            .max(20, "Must be 20 characters or less")
            .required("Required"),
          email: Yup.string()
            .email("Invalid email address")
            .required("Required"),
          Campus: Yup.string().oneOf(Campus).required("Required"),
          grade: Yup.number().required("Required"),
          academic: Yup.string().oneOf(Academic).required("Required")
        })}
        onSubmit={(values, { setSubmitting }) => {
          setTimeout(() => {
            alert(JSON.stringify(values, null, 2));
            setSubmitting(false);
          }, 400);
        }}
      >
        <Form>
          <MyTextInput label="이름" name="name" type="text" />
          <MyTextInput label="전화 번호" name="phoneNum" type="text" />
          <MyTextInput label="이메일" name="email" type="email" />
          <MyButtonBox name="Campus" buttonData={Campus} />
          <MyTextInput label="학년" name="grade" type="text" />
          <MyButtonBox name="academic" buttonData={Academic} />
          <button type="submit">Submit</button>
        </Form>
      </Formik>

기능만 구현하기 위한 간단한 구조이다.

formik의 공식문서 예시 템플릿을 참고했다.

initialValues

위의 값을 얻으려고 하는 값들의 초깃값을 지정했다.

validationSchema

Yup 라이브러리를 이용해 유효성 검사를 했다.

아직 완벽한 유효성 검사가 아닌 간단한 유효성 검사만 구현했다.

onSubmit

제출을 했을 때 values들을 모두 출력하도록 했다.

MyTextInput

input 컴포넌트를 따로 분리했다.

const MyTextInput = ({ label, ...props }) => {
  const [field, meta] = useField(props);
  return (
    <>
      <label htmlFor={props.id || props.name}>{label}</label>
      <input className="text-input" {...field} {...props} />
      {meta.touched && meta.error ? (
        <div className="error">{meta.error}</div>
      ) : null}
    </>
  );
};

useField 훅을 이용해 각 input에 관련된 값들을 field와 meta를 통해 받았다.

useField의 정의는 아래와 같다.

export declare function useField<Val = any>(propsOrFieldName: string | FieldHookConfig<Val>): [FieldInputProps<Val>, FieldMetaProps<Val>, FieldHelperProps<Val>];

그리고 FieldInput, FieldMeta, FieldHelper의 정의는 아래와 같다.

export interface FieldMetaProps<Value> {
    /** Value of the field */
    value: Value;
    /** Error message of the field */
    error?: string;
    /** Has the field been visited? */
    touched: boolean;
    /** Initial value of the field */
    initialValue?: Value;
    /** Initial touched state of the field */
    initialTouched: boolean;
    /** Initial error message of the field */
    initialError?: string;
}
/** Imperative handles to change a field's value, error and touched */
export interface FieldHelperProps<Value> {
    /** Set the field's value */
    setValue: (value: Value, shouldValidate?: boolean) => void;
    /** Set the field's touched value */
    setTouched: (value: boolean, shouldValidate?: boolean) => void;
    /** Set the field's error value */
    setError: (value: string | undefined) => void;
}
/** Field input value, name, and event handlers */
export interface FieldInputProps<Value> {
    /** Value of the field */
    value: Value;
    /** Name of the field */
    name: string;
    /** Multiple select? */
    multiple?: boolean;
    /** Is the field checked? */
    checked?: boolean;
    /** Change event handler */
    onChange: FormikHandlers['handleChange'];
    /** Blur event handler */
    onBlur: FormikHandlers['handleBlur'];
}

유용한 기능을 많이 제공해 주는 것 같다..

MyButtonBox

const MyButtonBox = ({ buttonData, name }) => {
  const [, meta, helpers] = useField(name);

  const { value } = meta;
  const { setValue } = helpers;

  const isSelected = (v) => (v === value ? "selected" : "");
  return (
    <>
      {buttonData.map((data) => (
        <button
          key={data}
          type="button"
          onClick={() => {
            setValue(data);
            console.log(value);
          }}
          className={() => isSelected(data)}
        >
          {data}
        </button>
      ))}
    </>
  );
};

button을 선택하는 예시 또한 formik 예시 코드에 있었다.

위처럼 이번엔 FieldHelper를 이용해 직접 선택된 값을 value에 넣어주는 작업을 해야 한다.

 

이제 잘 되는지 테스트해 보자.

값이 잘 저장된 것을 볼 수 있다.

 

728x90