React Native

Redux-sagaでAPIと通信中にLoading画面を表示する

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

初めに

APIを実行している時、Loading画面を表示できたらアプリっぽくなりますね。
なのでLoadingコンポーネントを作ってそいつを表示してみましょう
ReactNativeで実装します

画面実装

こちらの記事を参考に実装していきます。
少し長くて申し訳ないのですが上からコピペしていくだけで全然OKです。

React NativeでNavigationを使っていい感じに画面遷移する

Reduxがムズイのでtoolkitでどうにか簡単に実装する

Redux-sagaで簡単なAPIを動かしてみる

sliceの修正

まずはどちらか片方のsliceを修正します。
stateを追加して、API実行中と実行終了時にフラグを返します。

userSlice.js

import {createSlice} from '@reduxjs/toolkit';

const userInitialState = {
  userName: null,
  userEmail: null,
  img: null,
  // 新しいstateを定義、初期値はfalse
  isLoading: false,
};

export const userSlice = createSlice({
  name: 'user',
  initialState: userInitialState,
  reducers: {
    getInfo: state => {
      state.userName = 'Tanaka Taro';
      state.userEmail = 'practice@example.com';
    },
    resetInfo: state => {
      state.userName = userInitialState.userName;
      state.userEmail = userInitialState.userEmail;
    },
    // 画像取得開始時
    getImgStart: state => {
      state.isLoading = true;
    },
    // 画像取得完了時
    getImgSuccess: (state, {payload}) => {
      state.isLoading = false;
      state.img = payload;
    },
  },
});

export const {
  getInfo,
  resetInfo,
  getImgStart,
  getImgSuccess,
} = userSlice.actions;

getImageStartをした時にisLoadingをtrue
getImageSuccessの時にisLoadingをfalseにしてますね。

これによりLoadingコンポーネントを出し分けします(trueなら表示、falseなら表示しない方向で進めます)

sagaの名前を修正

getImgStartとgetImgSuccessにしたのでsagaの該当箇所を修正します

userSaga.js

import axios from 'axios';
import {put, call, takeLatest} from 'redux-saga/effects';
// 修正
import {getImgSuccess} from './userSlice';

function baseAxios() {
  const url = 'https://dog.ceo/api/breeds/image/random';
  return axios.get(url);
}

function* callApi() {
  try {
    const res = yield call(baseAxios);
    // 修正
    yield put(getImgSuccess(res));
  } catch (error) {
    console.log(error);
  }
}

export const getDog = () => ({type: 'GET_DOG'});

export function* userSaga() {
  yield takeLatest('GET_DOG', callApi);
}

コンポーネントの作成

簡単なLoadingコンポーネントを作成します。

まずはsrc配下にcomponentディレクトリを作成しましょう
さらにその中にLoading.jsとindex.jsを作成します

Loading.jsに関してはこんな感じで実装しました
コピペしてしまってOKです笑

Loading.js

import React from 'react';
import {StyleSheet, View, ActivityIndicator} from 'react-native';

const Loading = () => {
  return (
    <View style={styles.container}>
      <ActivityIndicator size="large" color="salmon" />
    </View>
  );
};

export default Loading;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignContent: 'center',
    justifyContent: 'center',
  },
});

index.jsではLoadingコンポーネントをexportするだけです
(Loading以外にもコンポーネントが多くなった時のためにexport用のファイルを分けています)

index.js

import Loading from './Loading';

export {Loading};

画面で表示してみる

HomeScreen.js上でLoadingコンポーネントを表示してみます

HomeScreen.js

import React from 'react';
import {StyleSheet, View, Text, Button, Image} from 'react-native';
// sliceからgetImgStartを新たにimport
import {getInfo, resetInfo, getImgStart} from '../redux/user/userSlice';
import {getDog} from '../redux/user/userSaga';
import {useSelector, useDispatch} from 'react-redux';
import {Loading} from '../component/';

const HomeScreen = ({navigation}) => {
  const dispatch = useDispatch();
  const user = useSelector(state => state.user);

  const dogImage = user.img?.data.message;

  const getImg = () => {
    // getImageStartでisLoadingをtrueにする
    dispatch(getImgStart());
    // 画像取得完了時にisLoadingはfalseになる
    dispatch(getDog());
  };

  // isLoadingの値で画面を出し分ける
  return user.isLoading ? (
    <Loading />
  ) : (
    <View style={styles.container}>
      <Text>私の名前は{user.userName}</Text>
      <Text>メールアドレスは{user.userEmail}</Text>
      <Image
        style={styles.img}
        source={{
          uri: dogImage,
        }}
      />
      <Button
        title="セットする"
        onPress={() => {
          dispatch(getInfo());
        }}
      />
      <Button
        title="リセットする"
        onPress={() => {
          dispatch(resetInfo());
        }}
      />
      <Button title="犬を取得" onPress={getImg} />
      <Button
        title="画面遷移する"
        onPress={() => {
          navigation.navigate('Detail');
        }}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  input: {
    width: '50%',
    borderWidth: 1,
  },
  img: {
    width: '75%',
    height: '50%',
  },
});

export default HomeScreen;

API通信中はこのようにLoading画面が表示されました。
画像データが取得されたらLoadingは消えて元の画面が表示されるはずです。


まとめ

  • sliceにはAPI実行中、Loading画面を表示するためのstateを追加して各actionに処理を記載
  • Loadingコンポーネントを作成する
  • 画面ではAPIを実行した時にLoadingコンポーネントが表示されるように記述を追加

簡単にですがこれでLoading画面が表示されるかと思います!