덕구공 2021. 9. 8. 20:43

Formik?

  • React로 웹을 만들다보면 우리는 사용자에게 다양한 인풋을 받게 된다.
  • 이때 모든 인풋을 각각의 state로 관리하면 코드가 매우 지저분해질 수도 있다.
  • Formik을 사용하면 이러한 form에 대한 state관리가 굉장히 편해진다! (나는 리덕스로 dynamic form을 만드려고 삽질을 했었다..)
  • Formik은 리액트 폼의 세가지 어려운 부분을 쉽게 처리할 수 있도록 도와준다!
    • form 상태의 안밖에서 값 가져오기
    • 유효성 검증 및 에러 메시지
    • 폼 제출 조작
  • 우선, 아래 명령어를 이용하여 formik을 설치하자.
// npm
npm install formik --save

// yarn
yarn add formik
 

Formik

React hooks and components for hassle-free form validation. The world's leading companies use Formik to build forms and surveys in React and React Native.

formik.org


<Formik />

  • form을 감싸며 form의 여러 상태를 관리해주는 컴포넌트로 볼 수 있다!
  • formik은 유용한 몇가지 props를 form에 넘겨준다.

  • Formik 컴포넌트 내에서 initialValues prop으로 초기 state를 json 형태로 지정할 수 있다.
  • initialValues는 values라는 이름의 props로 접근할 수 있고 input의 name 필드에 key값을 써주고 onChange에 Formik에서 디폴트 props로 넘겨준 handleChange를 넣어주기만 하면 state에 있는 해당 key가 매핑되어 값이 바뀔 때마다 자동으로 state가 업데이트된다!
  • onSubmit prop으로 submit이 되었을 때 처리할 일(함수, 비동기 통신 등등..)을 지정할 수 있고 번째 파라미터에 state가 들어간다. 그리고, handleSubmit이라는 props로 넘겨준다!
import React from 'react';
import { Formik } from 'formik';

const App = () => (
  <div>
    <Formik
      initialValues={{name: "처음 상태"}}
      onSubmit={(data) => {
        alert(JSON.stringify(data, null, 2));
      }}
    >
      {(props) => (
        <form onSubmit={props.handleSubmit}>
          <input value={props.values.name} onChange={props.handleChange} name="name"/>
          <button type="submit">변경된 상태 확인!</button>
        </form>
      )}
    </Formik>
  </div>
);

export default App;

touched, handleBlur

  • formik의 props 중 touched은 name에 상태의 키 값을 지정한 요소가 클릭되었는지 아닌지 true, false로 리턴한다.
  • 만약 name이 "duck"인 인풋이 클릭이 되었으면, props.touched.duck이 true가 될 것이다.
  • 이를 사용하기 위해 인풋의 onBlur나 onClick에 formik의 props인 handlBlur를 넣어주면 된다!
  • onBlur와 onClick의 차이점은 onBlur는 요소를 클릭한 후 다른 곳을 클릭하면 touched가 true로 변경되고, onClick은 요소를 클릭하면 그 즉시 touched가 true로 변경된다!
import React from 'react';
import { Formik } from 'formik';

const App = () => (
  <div>
    <Formik
      initialValues={{ onBlur: "onBlur", onClick: "onClick" }}
      onSubmit={(data) => {
        alert(JSON.stringify(data, null, 2));
      }}
    >
      {(props) => (
        <form onSubmit={props.handleSubmit}>
          <input
            onBlur={props.handleBlur}
            value={props.values.onBlur}
            onChange={props.handleChange}
            name="onBlur"
          />
          {props.touched.onBlur ? 
            <span>클릭했음</span> : 
            <span>아직 클릭 안함!</span>
          }
          <input
            onClick={props.handleBlur}
            value={props.values.onClick}
            onChange={props.handleChange}
            name="onClick"
          />
          {props.touched.onClick ? 
            <span>클릭했음</span> : 
            <span>아직 클릭 안함!</span>
          }
        </form>
      )}
    </Formik>
  </div>
);

export default App;

 


setValues()

  • Formik의 setValues() prop으로 state를 변경할 수 있다!
  • dynamic form을 만들 때 유용하게 쓰일 것 같다!
import React from 'react';
import { Formik } from 'formik';

const App = () => (
  <div>
    <Formik
      initialValues={{ name: "처음 상태" }}
      onSubmit={(data) => {
        alert(JSON.stringify(data, null, 2));
      }}
    >
      {(props) => (
        <form onSubmit={props.handleSubmit}>
          <button type="submit">처음 상태 확인!</button>
          <button
            onClick={() => {
              props.setValues({ name: "duck", height: 180 })
            }}
            type="submit"
          >
            변경된 상태 확인!
          </button>
        </form>
      )}
    </Formik>
  </div>
);

export default App;

<Field />

  • ㅈㄴ편함
  • Field 컴포넌트의 디폴트는 Html의 input 태그와 같지만 onChange를 명시하지 않아도 상태가 변경이된다!
  • 또한, onBlur나 onClick을 명시하지 않아도 touched의 상태를 추적할 수 있다.
  • 하지만 아무것도 명시하지 않으면 onBlur가 적용 되므로 onClick을 할 때 touched가 true로 변경되길 원한다면 onClick을 명시해야 한다!
  • Formik 안의 input태그와 마찬가지로 name 필드로 state의 key를 매핑할 수 있다!
  • 아래 두 코드는 같은 의미이다!
<input value={props.values.name} onChange={props.handleChange} onBlur={props.handleblur} name="name"/>
<Field value={props.values.name} name="name"/>
  • 예시는 아래와 같다!
import React from 'react';
import { Formik, Field } from 'formik';

const App = () => (
  <div>
    <Formik
      initialValues={{name: "처음 상태"}}
      onSubmit={(data) => {
        alert(JSON.stringify(data, null, 2));
      }}
    >
      {(props) => (
        <form onSubmit={props.handleSubmit}>
          <Field value={props.values.name} name="name"/>
          <button type="submit">변경된 상태 확인!</button>
        </form>
      )}
    </Formik>
  </div>
);

export default App;
  • as prop을 사용해서 html의 input, select, textarea처럼 지정할 수 있다!
import React from 'react';
import { Formik, Field } from 'formik';

const App = () => (
  <div>
    <Formik
      initialValues={{name: "duck", height: 180, food: ""}}
      onSubmit={(data) => {
        alert(JSON.stringify(data, null, 2));
      }}
    >
      {(props) => (
        <form onSubmit={props.handleSubmit}>
          <Field as="input" name="name"/>
          <Field as="textarea" name="height"/>
          <Field as="select"name="food">
            <option value="hamburger">hamburger</option>
            <option value="chicken">chicken</option>  
          </Field>
          <button type="submit">변경된 상태 확인!</button>
        </form>
      )}
    </Formik>
  </div>
);

export default App;

  • as prop을 select로 지정하고 name 필드를 지정하면 option에 따라 해당 name 필드의 상태가 변경된다!
import React from 'react';
import { Formik, Field } from 'formik';

const App = () => (
  <div>
    <Formik
      initialValues={{food: "hamburger"}}
      onSubmit={(data) => {
        alert(JSON.stringify(data, null, 2));
      }}
    >
      {(props) => (
        <form onSubmit={props.handleSubmit}>
          <Field as="select"name="food">
            <option value="hamburger">hamburger</option>
            <option value="chicken">chicken</option>  
          </Field>
          <button type="submit">상태 확인!</button>
        </form>
      )}
    </Formik>
  </div>
);

export default App;


<FieldArray />

  • state에 array로 된 value 값이 있을 때, FieldArray 컴포넌트의 name prop에 해당 array의 key를 적어주고 하위 Field 컴포넌트의 name prop에 빽틱으로 감싸서 name={`list.${idx}.text`} 와 같이 적어주면 리스트의 인덱스에 접근해서 값을 변경할 수 있다!
import React from 'react';
import { Formik, Field, FieldArray } from 'formik';

const App = () => (
  <div>
    <Formik
      initialValues={{
        list: [
          {text:" "},
          {text:" "},
          {text:" "}
        ]
      }}
      onSubmit={(data) => {
        alert(JSON.stringify(data, null, 2));
      }}
    >
      {(props) => (
        <form onSubmit={props.handleSubmit}>
          <FieldArray name="list">
            {()=>(props.values.list.map((item, idx)=>{
              return(
                <div key={idx}>
                  {`input ${idx+1}:`}<Field name={`list.${idx}.text`}/>
                </div>
              );
            }))

            }
          </FieldArray>
          <button type="submit">상태 확인!</button>
        </form>
      )}
    </Formik>
  </div>
);

export default App;

  • 만약 FieldArray 하위의 Field 컴포넌트의 name에 initialValues에 없는 key 값을 써주면 해당 key 값과 input 안의 내용이 업데이트 된다!

enableReinitialize

  • 리액트의 state나 useEffect를 통해 API를 호출을 통해서 응답받은 값으로 initialValues를 설정하고자 할 때, 이처럼 formik 외부의 값에 의해서 initialValues를 설정하면 해당 외부 값들이 변해도 initialValues가 변하지 않는다!!
  • 이 때 initialValues의 변함을 체크하는데는 얕은 비교가 사용된다.
  • Formik은 기본적으로 enableReinitialize값이 false로 주어지기 때문에 외부 값을 initialValues에 할당하고 해당 값을 변화시켜도 initialValues가 변하게 하려면 enableReinitialize값을 true로 주면 된다!
import React, { useEffect, useState } from "react";
import axios from "axios";
import { Formik } from "formik";
import * as Yup from "yup";

const SurveyView = (props) => {

  const [survey, setSurvey] = useState({})

  useEffect(() => {
    props.setMenu("surveylist")
    getSurvey(props.match.params.id);
  }, [])

  const getSurvey = async (id) => {
    const res = await axios.get(`/api/survey/${id}`);
    setSurvey(res.data)
  }

  const initialValues = {
    survey: survey
  }

  const handleSubmit = async (value) => {
    alert(JSON.stringify(value, null, 2));
  }

  return (
    <Formik initialValues={initialValues} onSubmit={handleSubmit}
      enableReinitialize={true}>
      {({ values, handleChange, setValues }) => (

        ...

        )}
    </Formik>
  );
}

export default SurveyView;