LoginSignup
4
6

More than 3 years have passed since last update.

Formikのv2が気になる!

Last updated at Posted at 2019-10-06

Formikのv2.0.1-rc.13で紹介されているuseField()が気になったので、使ってみました。
Hooksの対応が革新的そうです!

▼ Formik Releases v2.0.1-rc.13
https://github.com/jaredpalmer/formik/releases/tag/v2.0.1-rc.13

useField()の紹介は公式サイトを見てみてください。
Fomik 公式 useField()

導入

formikはTypescriptで書かれてるので、@typesライブラリのインストールは必要ありません!
Formik推奨のバリデーションライブラリ、Yupも入れておくことにします。

$ npm install formik yup @types/yup

それと、今回はreact-select<Select />タグを使いたかったので、そちらも入れておくことにします。
react-selectはセレクトボックスをいい感じに拡張してくれるライブラリです。

$ npm install react-select @types/react-select

フォームの完成形

まずは完成したイメージからお見せします!アンケートフォームを作ってみた感じです。

1.gif

シンプルなテキストエリア、セレクトボックスを置きました。「登録する」ボタンを押した時、コンソール画面に値が取得できているか確認して見ます。

2.png

実際のコード

<Formik />

フォームのコンポーネントに<Formik />タグを設置して、初期値(initialValues)や「登録」を押した時のonSubmit、バリデーション(validationSchema)を設置しています。

import { Formik, FormikProps } from "formik"
import React from "react"
import * as Yup from "yup"

export const SampleForm: React.FC = () => {
  return (
    <div>
      <h6>アンケートフォーム</h6>
      <Formik
        initialValues={initialValues}
        validationSchema={validationSchema}
        onSubmit={(values: FormValues) => { console.log(values) }}
        render={(props: FormikProps<FormValues>) => (
          <form onSubmit={props.handleSubmit}>
            {/* 各フォーム要素 */}
            <button type="submit">登録する</button>
          </form>
        )}
      />
    </div>
  )
}

ちなみに、先ほどの完成形で設定した初期値は下の通りです。

const initialValues: FormValues = {
  nickname: "",
  experience: true,
  message: "",
  rabbits: [],
  favoriteFoods: [],
  petName: [],
}

今回はバリデーションに付いて触れませんが、validationSchemaの書き方は公式サイトがとても分かりやすいです! → Formik公式 Validation

<input type="text" />

ニックネームとメッセージの入力エリアは、それぞれコンポーネントを分けて作りました。
<input />タグ用の<TextField />コンポーネントにnameだけ渡しています。

<Formik
  render={(props: FormikProps<FormValues>) => (
    ...
    <TextField name="nickname" />
    ...

下が<TextField />コンポーネントです。公式サイト通りですが、useField()を下のように使っています。

import React from "react"
import { useField } from "formik"

type Props = {
  name: string
}

export const TextField: React.FC<Props> = ({ name, ...props }) => {
  const [field, meta] = useField(name)

  return (
    <div>
      <input {...field} {...props} />
      {meta.error && meta.touched && <small>{meta.error}</small>}
    </div>
  )
}

今回は<Field />タグは使いませんでしたが、
Formik v2では、下のような<Field />タグでcomponentを指定するのは非推奨らしいです。

// 非推奨らしい
<Field name="firstName" component={CustomInputComponent} />
  
<Field name="firstName" as={CustomInputComponent} />

react-selectを使った<Select />

<Formik
  render={(props: FormikProps<FormValues>) => (
    ...
    <RabbitSelect
      name="rabbits"
      options={rabbitOptions}
      placeholder="1種類選択してください"
    />
    ...

optionsで渡しているのは、labelvalueで値をセットした配列です。

/**
 * うさぎの選択肢
 */
const rabbitOptions = [
  {
    label: "ネザーランド・ドワーフ",
    value: "netherland-dwarf"
  },
  {
    label: "ホーランド・ロップ",
    value: "holland-lop"
  },
  ...
]

<RabbitSelect />コンポーネントは下のように実装しました。
setFieldValue()も使いたかったので、useField()と合わせてuseFormikContext()を使っています。
as neverは妥協です。。あまり参考になさらないでください。)

import React from "react"
import Select from "react-select"
import { ValueType } from "react-select/src/types"
import { useFormikContext, useField } from "formik"

type OptionType = {
  label: string
  value: string
}

type Props = {
  name: string
  options: OptionType[]
  placeholder?: string
}

export const RabbitSelect: React.FC<Props> = ({
  name,
  options,
  placeholder,
}) => {
  const [field] = useField(name)
  const { setFieldValue } = useFormikContext()

  const onChange = React.useCallback((option) => {
    setFieldValue(
      field.name as never,
      (option as OptionType).value
    )
  }, [field.name, setFieldValue])

  const getValue = (): ValueType<OptionType> => {
    if (options) {
      return options.find(option => option.value === field.value)
    } else {
      return "" as any
    }
  }

  return (
    <Select
      name={field.name}
      value={getValue()}
      onChange={onChange}
      placeholder={placeholder}
      options={options}
    />
  )
}

今回はreact-selectについて触れませんが、複数選択できるのは<Select />isMultiを渡すだけです!
上の<RabbitSelect />と似たような記述です。

export const FoodSelect: React.FC<Props> = ({
  ... // 上と同じ
}) => {
  ... // 上と同じ
  const onChange = React.useCallback((option) => {
    setFieldValue(
      field.name as never,
      (option as OptionType[]).map((item) => item.value)
    )
  }, [field.name, setFieldValue])

  const getValue = (): ValueType<OptionType> => {
    if (options) {
      return options.filter(option => field.value.indexOf(option.value) >= 0)
    } else {
      return []
    }
  }

  return (
    <Select
      name={field.name}
      value={getValue()}
      onChange={onChange}
      placeholder={placeholder}
      options={options}
      isMulti
    />
  )
}

最後に

すみません、あまりuseField()の詳細について触れておりません。。とりあえず実装してみたかったので今回は備忘録がてら記事にさせて頂きました!まだ試していくので、都度更新していくことにします。

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6