8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

formik step by step (React Native)

Last updated at Posted at 2018-11-09

注意!!

本サンプルにはreact-native-elementsを利用していますが、旧執筆時はver0.xでした。が、最新は1.2.xであり、書き方が変わっています(Formが廃止されInputのみに)。最新環境でも動くコードを最後に追記してあります。

本文

React (Native)でのForm操作は悩ましいところです。
Redux-Formが有名みたいですが、Redux必須というのがうーんって感じです。

そこでformikを試してみたいと思います。

formikを利用することで値のハンドリング、バリデーション、Submitボタンの操作などいろいろできます。

書き方としては<Formik>タグを利用する方法とwithFormik()を利用する方法があるようですが、とりあえず<Formik>タグを利用してみます。

また、バリデーションはカスタムで書くか、Yupを利用するか(他のライブラリとも連携可能なようです)ですが、ここではYupを利用してみます。

React系のコーディングは完成形だけみても???って感じになるので冗長ですがSTEP BY STEPで進めていきます。

ゴール

最終的には下記のようなバリデーション付きのフォームを作ります。

スクリーンショット 2018-11-10 7.52.09.png

モジュールのインストール

必要なモジュールをインストールします。
UIのパーツとしてはreact-native-elementsを利用します。

npm install --save formik yup react-native-elements
npm install

STEP1:最低限の雛形を記述する

まず、項目としてNameを表示し、Submitでstateを表示する雛形を作ります。

App.js
import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements';

export default class App extends React.Component {
  render() {
    return (
      <View style={{flex:1, alignItems:'center', justifyContent:'center'}}>
        <Formik
          initialValues={{ name: ''}}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
        >
          {
            ({handleChange, handleSubmit, values}) => (
              <View style={{width:'80%'}}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
                  value={values.name}
                />
                <FormValidationMessage>error</FormValidationMessage>

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{marginTop:30}}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

onChangeText={handleChange('name')とすることで、nameに変更を反映してくれます。
また、value={values.name}として、値を表示します。

STEP2:項目を追加してみる

続いてEmailを追加してみます。特に難しいことはありません。

App.js
import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Formik
+          initialValues={{ name: '', email: '' }}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
        >
          {
            ({ handleChange, handleSubmit, values }) => (
              <View style={{ width: '80%' }}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
                  value={values.name}
                />
                <FormValidationMessage>error</FormValidationMessage>

+                <FormLabel>Email</FormLabel>
+                <FormInput
+                  onChangeText={handleChange('email')}
+                  value={values.email}
+                />
+                <FormValidationMessage>error</FormValidationMessage>

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{ marginTop: 30 }}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

STEP3:Validationの実装1:最低限

ではValidationを実装します。
下記のように記述することで、書くフォームのエラーを制御できるようになります。

App.js
import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements';
import * as Yup from 'yup';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Formik
          initialValues={{ name: '', email: '' }}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
+          validationSchema={Yup.object().shape({
+            name: Yup.string().required(),
+            email: Yup.string().email().required(),
+          })}
        >
          {
+            ({ handleChange, handleSubmit, values, errors }) => (
              <View style={{ width: '80%' }}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
                  value={values.name}
                />
+                <FormValidationMessage>{errors.name}</FormValidationMessage>

                <FormLabel>Email</FormLabel>
                <FormInput
                  onChangeText={handleChange('email')}
                  value={values.email}
                />
+                <FormValidationMessage>{errors.email}</FormValidationMessage>

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{ marginTop: 30 }}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

下記のようにすることでエラーメッセージをカスタマイズできます。

name: Yup
        .string()
        .required('名前の入力は必須です。'),

STEP4:Validationの実装2:対象の項目を操作するまでエラーを出さない

先述の状態では、どこか1つの項目をいじると、全ての項目についてバリデーションが実行され、エラーが表示されてしまいます。また編集(touch)していない項目についてはエラーを表示しないようにします。

{ (条件) && 表示内容}とすることで、条件により表示の切り替えが可能です。これを利用して、書く項目が編集されたかどうか(touched)で表示を制御します。

import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements';
import * as Yup from 'yup';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Formik
          initialValues={{ name: '', email: '' }}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
          validationSchema={Yup.object().shape({
            name: Yup.string().required(),
            email: Yup.string().email().required(),
          })}
        >
          {
+            ({ handleChange, handleSubmit, values, errors, touched }) => (
              <View style={{ width: '80%' }}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
                  value={values.name}
                />
+                {touched.name && <FormValidationMessage>{errors.name}</FormValidationMessage>}

                <FormLabel>Email</FormLabel>
                <FormInput
                  onChangeText={handleChange('email')}
                  value={values.email}
                />
+                {touched.email && <FormValidationMessage>{errors.email}</FormValidationMessage>}

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{ marginTop: 30 }}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

STEP5:Validationの実装3:項目からの移動時(onBlur)にバリデーションする

Submitボタンを押すまでエラーがわからないのは不親切です。
編集項目から移動した際(onBlur)にエラーがあれば表示するようにしてみます。

import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements';
import * as Yup from 'yup';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Formik
          initialValues={{ name: '', email: '' }}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
          validationSchema={Yup.object().shape({
            name: Yup.string().required(),
            email: Yup.string().email().required(),
          })}
        >
          {
+            ({ handleChange, handleSubmit, values, errors, touched, handleBlur }) => (
              <View style={{ width: '80%' }}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
+                  onBlur={handleBlur('name')}
                  value={values.name}
                />
                {touched.name && <FormValidationMessage>{errors.name}</FormValidationMessage>}

                <FormLabel>Email</FormLabel>
                <FormInput
                  onChangeText={handleChange('email')}
+                  onBlur={handleBlur('email')}
                  value={values.email}
                />
                {touched.email && <FormValidationMessage>{errors.email}</FormValidationMessage>}

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{ marginTop: 30 }}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

STEP6:Validationの実装4:全ての項目がOKになるまでSubmitボタンをアクティブにしない

場合により、全ての項目のチェックがOKになるまでSubmitボタンをアクティブにしたくない場合もあります。
それを試します。isValidを利用すればバリデーションがOKかNGかが取れるようです。

まあ、実際に適用するかどうかはケースバイケースかなと思います。

import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements';
import * as Yup from 'yup';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Formik
          initialValues={{ name: '', email: '' }}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
          validationSchema={Yup.object().shape({
            name: Yup.string().required(),
            email: Yup.string().email().required(),
          })}
        >
          {
+            ({ handleChange, handleSubmit, values, errors, touched, handleBlur, isValid }) => (
              <View style={{ width: '80%' }}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
                  onBlur={handleBlur('name')}
                  value={values.name}
                />
                {touched.name && <FormValidationMessage>{errors.name}</FormValidationMessage>}

                <FormLabel>Email</FormLabel>
                <FormInput
                  onChangeText={handleChange('email')}
                  onBlur={handleBlur('email')}
                  value={values.email}
                />
                {touched.email && <FormValidationMessage>{errors.email}</FormValidationMessage>}

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{ marginTop: 30 }}
+                  disabled={!isValid}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

STEP7:Validationの実装5:2度押し禁止

状況によりボタンの2通しを禁止したい場合もあります。それを実装してみます。isSubmittingでSubmitされたかどうかが取得できるようです。

import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements';
import * as Yup from 'yup';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Formik
          initialValues={{ name: '', email: '' }}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
          validationSchema={Yup.object().shape({
            name: Yup.string().required(),
            email: Yup.string().email().required(),
          })}
        >
          {
+            ({ handleChange, handleSubmit, values, errors, touched, handleBlur, isValid, isSubmitting }) => (
              <View style={{ width: '80%' }}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
                  onBlur={handleBlur('name')}
                  value={values.name}
                />
                {touched.name && <FormValidationMessage>{errors.name}</FormValidationMessage>}

                <FormLabel>Email</FormLabel>
                <FormInput
                  onChangeText={handleChange('email')}
                  onBlur={handleBlur('email')}
                  value={values.email}
                />
                {touched.email && <FormValidationMessage>{errors.email}</FormValidationMessage>}

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{ marginTop: 30 }}
+                  disabled={!isValid || isSubmitting}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

STEP8:Validationの実装6:Checkboxの追加

Checkboxの制御ができるか試してみます。
下記のようにすることで値のコントロール、バリデーションができましたが、ベストな記述かどうかわ微妙です。

import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
+import { Button, FormLabel, FormInput, FormValidationMessage, CheckBox } from 'react-native-elements';
import * as Yup from 'yup';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Formik
+          initialValues={{ name: '', email: '', check: false}}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
          validationSchema={Yup.object().shape({
            name: Yup.string().required(),
            email: Yup.string().email().required(),
+            check: Yup.boolean().oneOf([true],'同意にチェックして下さい。')
          })}
        >
          {
+            ({ handleChange, handleSubmit, values, errors, touched, handleBlur, isValid, isSubmitting, setFieldValue }) => (
              <View style={{ width: '80%' }}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
                  onBlur={handleBlur('name')}
                  value={values.name}
                />
                {touched.name && <FormValidationMessage>{errors.name}</FormValidationMessage>}

                <FormLabel>Email</FormLabel>
                <FormInput
                  onChangeText={handleChange('email')}
                  onBlur={handleBlur('email')}
                  value={values.email}
                />
                {touched.email && <FormValidationMessage>{errors.email}</FormValidationMessage>}

+                <CheckBox
+                  center
+                  title='同意'
+                  checked={values.check}
+                  onPress={() => setFieldValue('check',!values.check)}
+                />
+                {touched.check && <FormValidationMessage>{errors.check}</FormValidationMessage>}

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{ marginTop: 30 }}
                  disabled={!isValid || isSubmitting}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

おまけ:loading

最後に、formikとは関係ありませんが、Buttonにloadingを設定することでスピナーを表示できるようです。
外部API連携等の際は利用するといいかもしれません。

import React from 'react';
import { StyleSheet, Text, View, Alert } from 'react-native';
import { Formik } from 'formik';
import { Button, FormLabel, FormInput, FormValidationMessage, CheckBox } from 'react-native-elements';
import * as Yup from 'yup';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Formik
          initialValues={{ name: '', email: '', check: false}}
          onSubmit={values => Alert.alert(JSON.stringify(values))}
          validationSchema={Yup.object().shape({
            name: Yup.string().required(),
            email: Yup.string().email().required(),
            check: Yup.boolean().oneOf([true],'同意にチェックして下さい。')
          })}
        >
          {
            ({ handleChange, handleSubmit, values, errors, touched, handleBlur, isValid, isSubmitting, setFieldValue }) => (
              <View style={{ width: '80%' }}>

                <FormLabel>Name</FormLabel>
                <FormInput
                  onChangeText={handleChange('name')}
                  onBlur={handleBlur('name')}
                  value={values.name}
                />
                {touched.name && <FormValidationMessage>{errors.name}</FormValidationMessage>}

                <FormLabel>Email</FormLabel>
                <FormInput
                  onChangeText={handleChange('email')}
                  onBlur={handleBlur('email')}
                  value={values.email}
                />
                {touched.email && <FormValidationMessage>{errors.email}</FormValidationMessage>}

                <CheckBox
                  center
                  title='同意'
                  checked={values.check}
                  onPress={() => setFieldValue('check',!values.check)}
                />
                {touched.check && <FormValidationMessage>{errors.check}</FormValidationMessage>}

                <Button
                  onPress={handleSubmit}
                  title='Submit'
                  buttonStyle={{ marginTop: 30 }}
                  disabled={!isValid || isSubmitting}
+                  loading={true}
                />
              </View>
            )
          }
        </Formik>
      </View>
    );
  }
}

追記(2019年8月30日)

react-native-elementsが1.xとなり、Formが廃止され、Inputになりました。それに伴い上記のコードは動きません。下記のような感じにする必要があります。

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Card, Input, Button, CheckBox } from 'react-native-elements';
import { Formik } from 'formik';
import * as Yup from 'yup';

export default class App extends React.Component {

    state = {
        email: '',
        password: ''
    }

    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Formik
                    initialValues={{ email: '', password: '', check: false }}
                    onSubmit={values => alert(JSON.stringify(values))}
                    validationSchema={Yup.object().shape({
                        email: Yup.string().email('Emailの形式ではないようです。').required('Emailは必須です。'),
                        password: Yup.string().min(4, '4文字以上。').required('パスワードは必須です。'),
                        check: Yup.boolean().oneOf([true], '進むには同意してください。'),
                    })}
                >
                    {
                        ({ handleChange, handleSubmit, values, errors, touched, handleBlur, isValid, isSubmitting, setFieldValue }) => (
                            <View style={{ alignSelf: 'stretch', justifyContent: 'center', alignItems: 'center' }}>
                                <Input
                                    label="Email"
                                    placeholder="Input email address"
                                    containerStyle={{ marginTop: 20, width: '80%' }}
                                    autoCapitalize="none"
                                    errorMessage={errors.email && touched.email ? errors.email : null}
                                    value={values.email}
                                    onChangeText={handleChange('email')}
                                    onBlur={handleBlur('email')}
                                />
                                <Input
                                    placeholder="Input password."
                                    label="Password"
                                    containerStyle={{ marginTop: 20, width: '80%' }}
                                    errorMessage={errors.password && touched.password ? errors.password : null}
                                    secureTextEntry={true}
                                    value={values.password}
                                    onChangeText={handleChange('password')}
                                    onBlur={handleBlur('password')}
                                />
                                <CheckBox
                                    center
                                    title="同意する"
                                    containerStyle={{ width: '75%', borderColor: "#fff", backgroundColor: "#fff", marginTop: 30 }}
                                    checked={values.check}
                                    onPress={() => setFieldValue('check', !values.check)}
                                />
                                <Text style={{ fontSize: 12, color: 'red' }}>{errors.check && touched.check ? errors.check : null}</Text>
                                <Button
                                    title="Entry"
                                    buttonStyle={{ marginTop: 30, width: 200 }}
                                    onPress={handleSubmit}
                                    disabled={!isValid || isSubmitting}
                                />
                            </View>
                        )
                    }
                </Formik>
            </View>
        );
    }
}

参考

8
8
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
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?