ReactNativeで簡易コーポレートサイト（アプリ）を作成する #2 -Contact画面編-

# 概要
トップページと同様にお問合せページをReactNativeで再現します。
Contactコンポーネントを作成し、バリデーションを実装します。

# 完成イメージ
<img width="300" alt="000001.jpg" src="https://dl.dropbox.com/s/rzdw8uk61g28tsd/000001.jpg">
<img width="300" alt="000010.jpg" src="https://dl.dropbox.com/s/3wx3qb87ufcj7ba/000010.jpg">


# Contact画面作成
同様にContact.jsを作成していきます。

## Contact.js

```
touch src/screens/Contact.js
```

フォームを作成していきます。
HTMLが使えないのでそれぞれ作り直します。
TextInputコンポーネントを使って、inputとtextareaに相当するものを作成し、ボタンはTouchableOpacityコンポーネントを使います。
また、後にスタイルを設定するので`style={styles.名前}`の形式でスタイルを設定していきます。

```Contact.js
import React from "react";
import { View, StyleSheet, Text, ScrollView, TextInput, TouchableOpacity } from "react-native";
const Contact = () => {
    return (
        <ScrollView>
            <View style={styles.Content}>
                <Text style={styles.titleText}>お問い合わせ</Text>
                <View style={styles.formWrapper}>
                    <Text>お名前</Text>
                    <TextInput
                        placeholder="お名前"
                        numberOfLines={1}
                        style={styles.textInput}
                        autoCapitalize='none'
                    />
                    <Text style={styles.errorMessage}>お名前は必須です。</Text>

                    <Text>Email</Text>
                    <TextInput
                        placeholder="Email"
                        numberOfLines={1}
                        style={styles.textInput}
                        autoCapitalize='none'
                    />
                    <Text style={styles.errorMessage}>emailは必須かつemailの形式で入力してください。</Text>

                    <Text>お問合せ内容</Text>
                    <TextInput
                        placeholder="お問合せ内容"
                        multiline={true}
                        style={[styles.textArea]}
                    />
                    <Text style={styles.errorMessage}>お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</Text>
                    <TouchableOpacity style={styles.submitBtn}>
                        <Text>送信</Text>
                    </TouchableOpacity>

                </View>
            </View>
        </ScrollView>
    )
}

export default Contact;
```
できたら、その下に続けて以下のスタイルを記述していきます。

```javascript:Contact.js
const styles = StyleSheet.create({
    Content: {
        flex: 1,
        alignItems: "center",
        justifyContent: "center",
    },
    titleText: {
        fontWeight: "bold",
        paddingBottom: 10,
        paddingTop: 60,
    },
    formWrapper: {
        width: 300,
    },
    textInput: {
        backgroundColor: "#00000011",
        paddingHorizontal: 5,
        height: 30,
        marginVertical: 10,
    },
    textArea: {
        backgroundColor: "#00000011",
        paddingHorizontal: 5,
        height: 60,
        marginVertical: 10,
    },
    errorMessage: {
        marginBottom: 15,
        fontSize: 10,
        color: "red",
    },
    submitBtn: {
        alignItems: "center",
        justifyContent: "center",
        backgroundColor: "#00000033",
        height: 50,
    },
})
```


## 確認
一旦確認したいので、App.jsを修正してContact画面を表示させてみます。

```diff_javascript:App.js
    import React from "react";
    import Top from "./src/screens/Top";
+   import Contact from "./src/screens/Contact";

    export default function App() {
      return (
-       <Top />
+       <Contact />
      );
    }
```
<img width="300" alt="000020.jpg" src="https://dl.dropbox.com/s/as169wscjafv5d7/000020.jpg">


# バリデーション機能
見た目はできたので、機能を実装していきます。
reactでも利用したreact-hook-formを使ってバリデーションを設定していきます。

```
expo install react-hook-form
```

react-hook-formのインポート、APIのコールの部分はReactと全く同じです。
宣言の部分は、registerではなくcontrolとなっています。
これは、ReactNativeのようなUIが制御されているコンポーネントと合わせて使う際はcontrollerというコンポーネントを使っていきます。 Controller コンポーネント用に作成され、 React Hook Form に制御されたコンポーネントを登録するためのメソッドが含まれているオブジェクトがcontrolです。

```diff_javascript:Contact.js
    import React from "react";
    import React from "react";
    import { View, StyleSheet, Text, ScrollView, TextInput, TouchableOpacity } from "react-native";
+   import { useForm } from "react-hook-form";

    const Contact = () => {
+       const {
+           control,
+           handleSubmit,
+           formState: { errors }
+       } = useForm();

+   const onSubmit = (data) => {
+       const api_url = 'apiのURL';
+       fetch(api_url, {
+           method: "post",
+           headers: {
+               "Content-Type": "application/x-www-form-urlencoded",
+           },
+           body: encodeURI(`name=${data.name}&email=${data.email}&body=${data.body}`)
+       })
+           .then((response) => response.json())
+           .then((result) => alert(result.message))
+           .catch((error) => alert(error.message))
+   }
```

```javascript:コピペ用
const {
    control,
    handleSubmit,
    formState: { errors }
} = useForm();

const onSubmit = (data) => {
    const api_url = 'apiのURL';
    fetch(api_url, {
        method: "post",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
        },
        body: encodeURI(`name=${data.name}&email=${data.email}&body=${data.body}`)
    })
        .then((response) => response.json())
        .then((result) => alert(result.message))
        .catch((error) => alert(error.message))
}
```

送信ボタンを押したらonSubmitを呼び出すように設定します。onPressを使います。

```diff_javascript:Contact.js
+ <TouchableOpacity onPress={handleSubmit(onSubmit)} style={styles.submitBtn}>
- <TouchableOpacity style={styles.submitBtn}>
      <Text>Submit</Text>
  </TouchableOpacity>
```

name,email,bodyのTextInputをフォームに登録するために、それぞれのTextInputをControllerコンポーネントの中に入れます。Controllerコンポーネントでラップすることで値の同期が行えるようになります。
Controllerの中で、requiredとpatternオプションを指定します。
Controllerはname（必須）・control・render ・defaultValue・rulesといった引数を持ちます。
ControlはuseFormを呼び出すオブジェクトです。
renderはReact要素を返す関数です。onChange、onBlur、name、ref、valueを子コンポーネントを提供できます。このなかでTextInputを記述します。
onBlurはフォームからフォーカスが離れたときにバリデーションチェックが走るようにするために設定します。
onChangeTextは入力の度にバリデーションが走ります。


```diff_javascript:Contact.js
- import {useForm } from "react-hook-form";
+ import {useForm,Controller } from "react-hook-form";
```

まず、お名前部分を編集してみます。

```diff_javascript:Contact.js（お名前部分）
+   <Controller
+       name="name"  
+       control={control}
+       rules={{
+           required: "お名前は必須です。",
+       }}
+       render={({ field: { onChange, onBlur, value } }) => (
            <TextInput
                placeholder="お名前"
                numberOfLines={1}
                style={styles.textInput}
                autoCapitalize='none'
+               onBlur={onBlur}
+               onChangeText={onChange}
+               value={value}
            />
+       )}
+   />
-   <Text style={styles.errorMessage}>お名前は必須です。</Text>
+   {errors.name && <Text style={styles.errorMessage}>{errors.name.message}</Text>}
```
```javascript:コピペ用（お名前部分）
<Controller
    name="name"
    control={control}
    rules={{
        required: "お名前は必須です。",
    }}
    render={({ field: { onChange, onBlur, value } }) => (
        <TextInput
            placeholder="お名前"
            numberOfLines={1}
            style={styles.textInput}
            autoCapitalize='none'
            onBlur={onBlur}
            onChangeText={onChange}
            value={value}
        />
    )}
/>
{errors.name && <Text style={styles.errorMessage}>{errors.name.message}</Text>}
```

次にemail部分です。

```diff_javascript:Contact.js（email部分）
    <Text>Email</Text>
+   <Controller
+       name="email"  
+       control={control}
+       rules={{
+           required: "emailは必須です。",
+           pattern: {
+               value: /^[a-z0-9.]+@[a-z0-9.]+\.[a-z]+$/,
+               message: "emailは必須かつemailの形式で入力してください。"
+           },
+       }}
+       render={({ field: { onChange, onBlur, value } }) => (
            <TextInput
                placeholder="Email"
                numberOfLines={1}
                style={styles.textInput}
                autoCapitalize='none'
+               onBlur={onBlur}
+               onChangeText={onChange}
+               value={value}
            />
+       )}
+   />
-   <Text style={styles.errorMessage}>emailは必須かつemailの形式で入力してください。</Text>
+   {errors.email && <Text style={styles.errorMessage}>{errors.email.message}</Text>}
```

```javascript:コピペ用（email部分）
<Controller
    name="email"
    control={control}
    rules={{
        required: "emailは必須です。",
        pattern: {
            value: /^[a-z0-9.]+@[a-z0-9.]+\.[a-z]+$/,
            message: "emailは必須かつemailの形式で入力してください。"
        },
    }}
    render={({ field: { onChange, onBlur, value } }) => (
        <TextInput
            placeholder="Email"
            numberOfLines={1}
            style={styles.textInput}
            autoCapitalize='none'
            onBlur={onBlur}
            onChangeText={onChange}
            value={value}
        />
    )}
/>
{errors.email && <Text style={styles.errorMessage}>{errors.email.message}</Text>}
```

最後にお問合せ内容部分です。

```diff_javascript:Contact.js(お問合わせ内容部分）
    <Text>お問合せ内容</Text>
+   <Controller
+       name="body"
+       control={control}
+       rules={{
+           required: "お問い合わせは必須です。",
+           maxLength: {
+               value: 10,
+               message: "お問合せ内容は必須かつ1文字以上10文字以下で入力してください。"
+           },
+       }}
+       render={({ field: { onChange, onBlur, value } }) => (
            <TextInput
                placeholder="お問合せ内容"
                multiline={true}
                style={[styles.textArea]}
+               onBlur={onBlur}
+               onChangeText={onChange}
+               value={value}
            />
+       )}
+   />
+   {errors.body && <Text style={styles.errorMessage}>{errors.body.message}</Text>}
-   <Text style={styles.errorMessage}>お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</Text>
```

```javascript:コピペ用（お問合せ内容部分）
<Controller
    name="body"
    control={control}
    rules={{
        required: "お問い合わせは必須です。",
        maxLength: {
            value: 10,
            message: "お問合せ内容は必須かつ1文字以上10文字以下で入力してください。"
        },
    }}
    render={({ field: { onChange, onBlur, value } }) => (
        <TextInput
            placeholder="お問合せ内容"
            multiline={true}
            style={[styles.textArea]}
            onBlur={onBlur}
            onChangeText={onChange}
            value={value}
        />
    )}
/>
{errors.body && <Text style={styles.errorMessage}>{errors.body.message}</Text>}
```

保存して確認します。
<img width="300" alt="000030.jpg" src="https://dl.dropbox.com/s/r9dvc2yyhoxcmd8/000030.jpg">
<img width="300" alt="000040.jpg" src="https://dl.dropbox.com/s/re5t3pbh6f7qi1u/000040.jpg">

# 最終的なコード

## Contact.js

```Contact.js
import React from "react";
import { View, StyleSheet, Text, ScrollView, TextInput, TouchableOpacity } from "react-native";
import { useForm, Controller } from "react-hook-form";

const Contact = () => {
    const {
        control,
        handleSubmit,
        formState: { errors }
    } = useForm();

    const onSubmit = (data) => {
        const api_url = 'apiのURL';
        fetch(api_url, {
            method: "post",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded",
            },
            body: encodeURI(`name=${data.name}&email=${data.email}&body=${data.body}`)
        })
            .then((response) => response.json())
            .then((result) => alert(result.message))
            .catch((error) => alert(error.message))
    }
    return (
        <ScrollView>
            <View style={styles.Content}>
                <Text style={styles.titleText}>お問い合わせ</Text>
                <View style={styles.formWrapper}>
                    <Text>お名前</Text>
                    <Controller
                        name="name"
                        control={control}
                        rules={{
                            required: "お名前は必須です。",
                        }}
                        render={({ field: { onChange, onBlur, value } }) => (
                            <TextInput
                                placeholder="お名前"
                                numberOfLines={1}
                                style={styles.textInput}
                                autoCapitalize='none'
                                onBlur={onBlur}
                                onChangeText={onChange}
                                value={value}
                            />
                        )}
                    />
                    {errors.name && <Text style={styles.errorMessage}>{errors.name.message}</Text>}

                    <Text>Email</Text>
                    <Controller
                        name="email"
                        control={control}
                        rules={{
                            required: "emailは必須です。",
                            pattern: {
                                value: /^[a-z0-9.]+@[a-z0-9.]+\.[a-z]+$/,
                                message: "emailは必須かつemailの形式で入力してください。"
                            },
                        }}
                        render={({ field: { onChange, onBlur, value } }) => (
                            <TextInput
                                placeholder="Email"
                                numberOfLines={1}
                                style={styles.textInput}
                                autoCapitalize='none'
                                onBlur={onBlur}
                                onChangeText={onChange}
                                value={value}
                            />
                        )}
                    />
                    {errors.email && <Text style={styles.errorMessage}>{errors.email.message}</Text>}

                    <Text>お問合せ内容</Text>
                    <Controller
                        name="body"
                        control={control}
                        rules={{
                            required: "お問い合わせは必須です。",
                            maxLength: {
                                value: 10,
                                message: "お問合せ内容は必須かつ1文字以上10文字以下で入力してください。"
                            },
                        }}
                        render={({ field: { onChange, onBlur, value } }) => (
                            <TextInput
                                placeholder="お問合せ内容"
                                multiline={true}
                                style={[styles.textArea]}
                                onBlur={onBlur}
                                onChangeText={onChange}
                                value={value}
                            />
                        )}
                    />
                    {errors.body && <Text style={styles.errorMessage}>{errors.body.message}</Text>}
                    <TouchableOpacity onPress={handleSubmit(onSubmit)} style={styles.submitBtn}>
                        <Text>送信</Text>
                    </TouchableOpacity>

                </View>
            </View>
        </ScrollView>
    )
}

export default Contact;

const styles = StyleSheet.create({
    Content: {
        flex: 1,
        alignItems: "center",
        justifyContent: "center",
    },
    titleText: {
        fontWeight: "bold",
        paddingBottom: 10,
        paddingTop: 60,
    },
    formWrapper: {
        width: 300,
    },
    textInput: {
        backgroundColor: "#00000011",
        paddingHorizontal: 5,
        height: 30,
        marginVertical: 10,
    },
    textArea: {
        backgroundColor: "#00000011",
        paddingHorizontal: 5,
        height: 60,
        marginVertical: 10,
    },
    errorMessage: {
        marginBottom: 15,
        fontSize: 10,
        color: "red",
    },
    submitBtn: {
        alignItems: "center",
        justifyContent: "center",
        backgroundColor: "#00000033",
        height: 50,
    },
})
```

## App.js

```App.js
import React from "react";
import Top from "./src/screens/Top";
import Contact from "./src/screens/Contact";

export default function App() {
  return (
    <Contact />
  );
}
```

# 関連コンテンツ
* 次：[ReactNativeで簡易コーポレートサイト（アプリ）を作成する #3 -タブバー編-](https://qiita.com/minimumskills/private/4fa150c98b7afc0ab79c)
* 前：[ReactNativeで簡易コーポレートサイト（アプリ）を作成する #1 -TOP画面編-](https://qiita.com/minimumskills/private/44d607d9e6e375aace0b)
* [超シンプルなWeb（アプリ）作成を通して各種フロントエンド技術を体験する - 2023 Early -](https://qiita.com/minimumskills/private/9e7efe86e244bb9844b0)