React Native

ReactNativeでログイン機能を実装してみた。

React Native
この記事は約28分で読めます。

概要

今回はReactNativeでログイン機能を作っていきたいと思います。
私が投稿した記事の「初心者がReduxToolkitを使ってみた」をベースに進めていきますので、適宜そちらを参照して読み進めていただけると幸いです。
本記事の対象の方は「最近React,ReactNativeを学習し始めました!」くらいのレベル感でしょうか。
コードが若干長くなりますことご了承ください。

導入

まず今回使うライブラリですが、
redux,
firebase,
react navigationを使っていきます。
※何がライブラリで何がライブラリではないのか。フレームワークとは何か。私自身未だにわかりません。

早速ですが、それぞれをinstallしていきます。
まずプロジェクトを作成。

react-native init LoginSample

Reduxのインストール

npm install redux

react navigationのインストール

npm install @react-navigation/native
npm install @react-navigation/drawer

firebaseのインストール


npm install -g firebase-tools
npm install --save firebase

firebaseの設定

firebase公式
まず上記からアカウントの作成もしくはログインをしてください

まずログインしたらアプリを作成⇨アプリ名は自由に決めて、アナリティクスは有効でも無効でも問題ありません。

次に左側のメニューからAuthenticationを選択し、一番上の「メール/パスワード」を選択。⇨有効にする

次に左側メニュー内の歯車マークの設定を選択。
リソースロケーションを「asia-northeast1」で選択。(一度選択すると変更できません)

ここまで行ったらお使いのターミナル移動してください。
今回のプロジェクトのディレクトリへ移動し、firebaseへログインします

firebase login

次にinitで作成したローカル上のアプリと、firebaseで作成したアプリを接続します

firebase init

ここに関しては該当のプロジェクトで使用するものを選んで進んでください。

これで準備できました。
React native Firebaseのページもありますので適宜参照してください。

コード

※コードが長くなります。こちらからシュミレーターでの確認まで飛べます。

まずfirebaseの設定ファイルから。

/firebase/config.js

import firebase from 'firebase';

// Firebase 初期化
const config = {
    //下記の値はFirebaseコンソール>プロジェクトの設定に記載されてますのでコピペしてください
    apiKey: 
    authDomain: 
    projectId: 
    storageBucket: 
    messagingSenderId: 
    appId: 
    measurementId: 
};
export const firebaseApp = firebase.initializeApp(config)

// ユーザ登録
export const signup = (email, password) => {
    firebase.auth().createUserWithEmailAndPassword(email, password)
        .then(user => {
            if (user) {
                console.log("Success to Signup")
            }
        })
        .catch(error => {
            console.log(error);
        })
}

// メール&パスワードログイン
export const login = (email, password) => {
    firebase.auth().signInWithEmailAndPassword(email, password)
        .then(response => {
            alert("Login Success!");
        })
        .catch(error => {
            alert(error.message);
        });
}

export default firebase;

App.js

import React from 'react';
import {Provider} from 'react-redux';
import createStore from './redux/store/Store';
import Router from './components/Router';

export const store = createStore();

const App = () => {
  return (
    <Provider store={store}>
      <Router />
    </Provider>
  );
};

export default App;

画面遷移の処理を記載するRouter.js

src/component/Router.js

import React from 'react';
import {createDrawerNavigator} from '@react-navigation/drawer';
import {NavigationContainer} from '@react-navigation/native';
import SignUpScreen from '../screens/SignUpScreen';
import SignInScreen from '../screens/SignInScreen';
import SignOutScreen from '../screens/SignOutScreen';
import {useSelector} from 'react-redux';
import Home from '../screens/Home';

const Drawer = createDrawerNavigator();

const Router = () => {
  const selector = useSelector((state) => state);

  return (
    <>
      {selector.users.isSignedIn ? (
        <NavigationContainer>
          <Drawer.Navigator initialRouteName="Home">
            <Drawer.Screen name="Home" component={Home} />
            <Drawer.Screen name="SignOutScreen" component={SignOutScreen} />
          </Drawer.Navigator>
        </NavigationContainer>
      ) : (
        <NavigationContainer>
          <Drawer.Navigator initialRouteName="SignInScreen">
            <Drawer.Screen name="SignInScreen" component={SignInScreen} />
            <Drawer.Screen name="SignUpScreen" component={SignUpScreen} />
          </Drawer.Navigator>
        </NavigationContainer>
      )}
    </>
  );
};

export default Router;

次にサインイン画面のSignInScreen.js

src/screen/SignInScreen.js

import React, {useState} from 'react';
import {Button,View,Text,SafeAreaView,TextInput,Alert,} from 'react-native';
import firebase from '../firebase/config';
import {useDispatch} from 'react-redux';
import {signInAction} from '../redux/users/Action';
import {jpCheck,blankCheckEmail,blankCheckPassword,checkEmailFormat,} from '../utils/index';
import ErrorMessage from '../components/ErrorMessage';

const SignInScreen = ({navigation}) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const dispatch = useDispatch();

  const signIn = (email, password) => {
    //Emailに日本語が含まれないかのCheck
    const isJapanese = jpCheck(email);
    //Emailが空文字ではないかのCheck
    const isBlankEmail = blankCheck(email);
    //Passwordが空文字ではないかのCheck
    const isBlankPassword = blankCheck(password);
    //Emailのフォーマットが正しいかのCheck
    const isFormatAddress = checkEmailFormat(email);
    if (isJapanese || isBlankEmail || isBlankPassword || isFormatAddress) {
      Alert.alert('入力に誤りがあります。正しく入力してください');
      return;
    } else {
      firebase
        .auth()
        //Config.jsで定義した関数でログインの処理をする
        .signInWithEmailAndPassword(email, password)
        .then((user) => {
          if (user) {
            //ログイン状態のstateをtrueにしてRouterに処理を戻す
            dispatch(signInAction({userEmail: email, userPassword: password}));
            return;
          }
        })
        .catch((error) => {
          if (
            //ログインに失敗した場合の処理
            error.message ===
            'There is no user record corresponding to this identifier. The user may have been deleted.'
          ) {
            Alert.alert('アカウントが見つかりません');
          } else {
            Alert.alert('エラーです。');
          }
        });
    }
  };

  return (
    <SafeAreaView>
      <View>
        <Text>サインイン</Text>
      </View>
      <View>
        <View >
          <TextInput
            placeholder={'メールアドレス'}
            value={email}
            autoCapitalize="none"
            onChangeText={setEmail}
          />
        </View>
        <ErrorMessage email={email} typedText={email} />
        <View >
          <TextInput
            placeholder={'パスワード'}
            type={'password'}
            secureTextEntry={true}
            onChangeText={setPassword}
          />
        </View>
        <ErrorMessage password={password} typedText={password} />
        <View >
          <Button title="サインイン" onPress={() => signIn(email, password)} />
        </View>
        <View >
          <View >
            <Text>アカウントをお持ちでない方は下記よりご登録ください。</Text>
          </View>
        </View>
        <View>
          <Button
            title="新規登録はこちらから"
            onPress={() => navigation.navigate('SignUpScreen')}
          />
        </View>
      </View>
    </SafeAreaView>
  );
};

export default SignInScreen;

アカウントがない場合に登録するSignUpScreen.js

src/screen/SignInScreen.js

import React, { useState } from "react";
import { Button, View, Text, SafeAreaView, StyleSheet, TextInput, Alert } from "react-native";
import firebase from '../firebase/config';
import { useDispatch } from "react-redux"
import { signInAction } from '../redux/users/Action';
import { jpCheck, blankCheck, checkEmailFormat } from "../utils/index"
import ErrorMessage from "../components/ErrorMessage"

const SignUpScreen = ({ navigation }) => {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [confirmPassword, setConfirmPassword] = useState("")
  const dispatch = useDispatch();

  const signUp = (email, password, confirmPassword) => {
    const isJapanese = jpCheck(email)
    const isBlankEmail = blankCheck(email)
    const isBlankPassword = blankCheck(password)
    const isBlankConfirmPassword = blankCheck(confirmPassword)
    const isFormatAddress = checkEmailFormat(email)
    const isMatchPassword = password !== confirmPassword
    if (isJapanese || isBlankEmail || isBlankPassword || isFormatAddress || isBlankConfirmPassword || !isMatchPassword) {
      Alert.alert('入力に誤りがあります。正しく入力してください')
      return
    } else {
      firebase.auth().createUserWithEmailAndPassword(email, password)
          .then(user => {
            if (user) {
              Alert.alert("アカウントの登録が完了しました")
                dispatch(signInAction({ userEmail: email, userPassword: password }));
                return
            }
          })
          .catch(error => {
            if (error.message === "The email address is already in use by another account.") {
                Alert.alert("すでに登録されているメールアドレスです。")
            } else if (error.message === "Password should be at least 6 characters") {
                Alert.alert("パスワードは6文字以上で登録してください。")
            } else {
                Alert.alert("エラーです。異る入力内容でもう一度お試しください")
                  console.log(error.message);
            }
         })
      }
   }
  return (
    <SafeAreaView>
        <View>
           <Text style={{ fontSize: 20 }}>アカウント登録</Text>
        </View>
        <View>
           <View>
              <TextInput
                 placeholder="メールアドレス"
                 autoCapitalize='none'
                 onChangeText={setEmail}
              />
           </View>
           <ErrorMessage email={email} typedText={email} />
           <View>
              <TextInput
                 placeholder="パスワード"
                 type="password"
                 secureTextEntry={true}
                 onChangeText={setPassword}
              />
           </View>
           <ErrorMessage password={password} typedText={password} />
           <View>
              <TextInput
                 placeholder="確認用パスワード"
                 type="confirmPassword"
                 secureTextEntry={true}
                 onChangeText={setConfirmPassword}
              />
           </View>
           <ErrorMessage password={password} confirmPassword={confirmPassword} typedText={confirmPassword} />
             <View>
                <Button
                   title="送信"
                   onPress={() => signUp(email, password)}
                />
            </View>
                <View>
                   <View>
                      <Text>・全ての項目を入力してください。</Text>
                   </View>
                <View>
                   <Text>・メールアドレスは[@]があり、[@]後に1つ以上の[.]が</Text>
                </View>
                <View>
                   <Text>   なければ登録できません。</Text>
                </View>
                <View>
                   <Text>・赤文字が出力されている状態では登録できません。</Text>
                </View>
                <View>
                   <Text>・パスワードは6文字以上で入力してください。</Text>
                </View>
                </View>
            <View>
                <Button
                   title="サインイン画面へ戻る"
                   onPress={() => navigation.navigate("SignInScreen")}
                />
            </View>
         </View>
      </SafeAreaView>
   )
}
export default SignUpScreen

Home.jsは「初心者がReduxToolkitを使ってみた」こちらのHome.jsと同じものとします。src/screen/SignOutScreen.js

import React from 'react';
import { Button, View, Text, StyleSheet, SafeAreaView } from 'react-native';
import { useDispatch } from "react-redux"
import { signOutAction } from '../redux/users/Action';

const SignOutScreen = () => {
    const dispatch = useDispatch()
    const submitSignOut = () => {
        dispatch(signOutAction())
        return
    }
    return (
        <SafeAreaView>
            <View>
                <Text>Sign Out</Text>
                <View>
                    <Button
                        title='サインアウトする'
                        onPress={submitSignOut}
                    />
                </View>
            </View>
        </SafeAreaView>
    )
}

export default SignOutScreen

空文字入力などの関数をまとめているファイル

src/util/index.js


//アドレスの日本語入力チェック
const jpCheck = (Email) => {
    const regexEmail = /[亜-熙ぁ-んァ-ヶ]/;
    return regexEmail.test(Email);
};

//アドレス,パスワードの空文字チェック
const blankCheck = (props) => {
    const regexEmail = /[^\s ]/;
    return !regexEmail.test(props);
};

//アドレスの形式チェック('英数字' + @ + '英数字' + . + '英数字'の形式のみ可)
const checkEmailFormat = (Email) => {
    const regexEmail = /^[a-zA-Z0-9_+-]+(.[a-zA-Z0-9_+-]+)*@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$/;
    return !regexEmail.test(Email);
};

export {jpCheck,blankCheck,checkEmailFormat,};

Input欄下のエラーメッセージを出力するErrorMessage.js

src/component/ErrorMessage.js

import React, { useState, useEffect } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import {jpCheck, blankCheck, checkEmailFormat,} from '../utils/index';

const ErrorMessage = ({ email, password, confirmPassword, typedText }) => {
    const [emailErrorMessage, setEmailErrorMessage] = useState('');
    const [passwordErrorMessage, setPasswordErrorMessage] = useState('');
    const [confirmPasswordErrorMessage, setConfirmPasswordErrorMessage] = useState('');

    useEffect(() => {
        const isJapanese = jpCheck(email);
        const isBlankEmail = blankCheck(email);
        const isFormatEmail = checkEmailFormat(email);
        if (isJapanese) {
            setEmailErrorMessage('日本語は含めずに入力してください');
        } else if (isBlankEmail) {
            setEmailErrorMessage('必須項目です。入力してください');
        } else if (isFormatEmail) {
            setEmailErrorMessage('正しいメールアドレス形式で入力してください');
        } else {
            setEmailErrorMessage('');
        }
    }, [email])

    useEffect(() => {
        const isBlankPassword = blankCheck(password);
        if (isBlankPassword) {
            setPasswordErrorMessage('必須項目です。入力してください');
        } else {
            setPasswordErrorMessage('');
        }
    }, [password]);

    useEffect(() => {
        const isBlankPassword = blankCheckConfirmPassword(confirmPassword);
        if (isBlankPassword) {
            setConfirmPasswordErrorMessage('必須項目です。入力してください');
        } else if (password !== confirmPassword) {
            setConfirmPasswordErrorMessage('パスワードが一致しません')
        } else {
            setConfirmPasswordErrorMessage('');
        }
    }, [confirmPassword]);


    if (typedText === password) {
        return (
            <View>
                <Text style={styles.text}>
                    {passwordErrorMessage}
                </Text>
            </View>
        )
    } else if (typedText === email) {
        return (
            <View>
                <Text style={styles.text}>
                    {emailErrorMessage}
                </Text>
            </View>
        )
    } else {
        return (
            <View>
                <Text style={styles.text}>
                    {confirmPasswordErrorMessage}
                </Text>
            </View>
        )
    }
}

export default ErrorMessage

以上です!
Home画面のHome.jsと、reduxのstore,action,reducerは「初心者がReduxToolkitを使ってみた」こちらと全く同じものを使ってますのでご参照ください。
尚、Styleは省略してます。

シュミレーターでの動き

起動して最初はサインイン画面へ飛びます。
「新規登録はこちらから」をタップするとアカウント登録画面へ飛びます。

入力欄では
・空文字 ⇨ 必須項目です。入力してください。
・Emailのフォーマットに合致していない ⇨ 正しいメールアドレス形式で入力してください。
・日本語入力 ⇨ 日本語は含めずに入力してください。
・パスワードと確認パスワードが不一致 ⇨ パスワードが一致しません。
と表示されるようにしてます。
エラーメッセージが表示されている状態で送信すると、アラートで「入力に謝りがあります。正しく入力してください」と表示。
サインイン画面でもほぼ同様の動きです。

入力欄にエラーがない状態で送信すると、ホームに遷移してアラートで「アカウント登録が完了しました」と表示。
ドロワーからサインアウトを選択し、サインアウトするタップでサインイン画面へ遷移します。

以上が今回実装したものになります。
Firebaseコンソールで、入力したものがちゃんと登録されているかみてみましょう。
Firebaseコンソール>該当のプロジェクト>左のメニューからAuthenticationで確認できます。

感想

いかがでしたでしょうか。
今回投稿させて頂いた内容が、私が入社前に書いたもので今見ると中々にイケてないですね。
何せ関数名、変数名がぐちゃぐちゃです。
その初々しいコードを公開するのも良いかなということで、そのまま投稿いたします。
スタイルに関しては書き方があまりにも杜撰で、未だに上手く書けない上にコードが長くなるので省略いたしました。

もっとこうした方が良い等ありましたら編集リクエストをしてくださると幸いです。
以上です。