こんにちは。西日本テクノロジー&イノベーション室の藤田です。 日々(技術的に)強くなりたいと言っている新卒3年目です。

この記事では、先日まで関わっていたサービス開発案件のノウハウ紹介として、ReactでAPI通信している間にLoading画面を表示する実装について説明します。

背景

React + Railsでログインを必要とするサービス開発をしています。

このサービスは、サービスを利用しているユーザーのログイン状態に応じて振る舞いを切り替えています。

例えば、ユーザーがログイン済みであればユーザー自身の利用履歴が閲覧できますが、ログインしていない状態でそれはできません。ログイン状態に応じて振る舞いが変わるのは、ログインが必須のサービスであれば一般的な仕様だと思います。

ユーザーのログイン状態は、ユーザー自身の情報を取得するAPIを叩いて判断します。ログイン済みであればユーザー情報が返ってきますし、ログインしていなければ401エラーが返ってくるようにしています。

ユーザー情報を取得した結果によって表示するコンポーネントを切り替えるため、ユーザー情報を取得している間に表示するLoading画面が必要でした。 ここでは、Loading画面をどのように切り替えて表示しているのかをコードを例示しながら説明します。

実装

このサービスではLoading画面の切り替えを、最上位のコンポーネントでありルーティングを管理しているApp.jsに実装しました。全てのコンポーネントにユーザー情報を共有する必要があったためです。 以下で、App.jsに実装した内容を説明していきます。

Loading画面を表示するフラグをstateに設定しておく

あらかじめloadingというstatetrueの状態で持っておきます。

 constructor(props) {
    super(props);
    this.state = {
      loading: true
    };
  }

ユーザー情報を取得した後にstateに設定したフラグを切り替える

componentDidMount()でユーザー情報を取得して、結果をuserに格納してsetStateします。もしログインしていなかった場合は401エラーが発生するので、空のオブジェクトをuserに格納し同じようにsetStateします。

また、一連の処理が終わった時点で、loadingfalseに設定します。

  async componentDidMount() {
    try {
      const resp= await axios.get("/api/my/profile");
      const user = resp.data;
      this.setState({ user });
    } catch (e) {
      if (e.response.status === 401) {
        const user = {};
        this.setState({ user });
      } else {
        //401エラー以外のエラーが返ってきた場合の処理を記述
      }
    }
    this.setState({ loading: false });
  }

stateのフラグによって表示する画面を切り替える

loadingtrueであればLoading画面を表示し、falseであれば、アプリケーションの画面を表示させます。 Loading画面ではAnt Design MobileのActivityIndicatorを使っています。 ユーザー情報はContextを用いて下位コンポーネントに共有しています。

AppContextReact.createContext()で作成したContextオブジェクトです。 ここでは、valueuserを指定して下位コンポーネントにユーザー情報を共有しています。

  render() {
    const { loading, user } = this.state;
    const style = { position: "fixed", top: 0, left: 0, width: "100%", height: "100%", display: "flex", justifyContent: "center", alignItems: "center" };
    if (loading) {
      return (
      <div style={style}>
        <ActivityIndicator size="large" />
      </div>
      );
    }
    return (
      <AppContext.Provider value={{ user }}>
        <BrowserRouter>
          {/*ここで各コンポーネントのルーティングを設定する*/}
        </BrowserRouter>
      </AppContext.Provider>
    );
  }

以上の説明を踏まえて、App.jsの全体のコードは以下の通りです。

import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import axios from "axios";
import { ActivityIndicator } from "antd-mobile";
import AppContext from "./context";

class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      loading: true
    };
  }

  async componentDidMount() {
    try {
      const resp= await axios.get("/api/my/profile");
      const user = resp.data;
      this.setState({ user });
    } catch (e) {
      if (e.response.status === 401) {
        const user = {};
        this.setState({ user });
      } else {
        //401エラー以外のエラーが返ってきた場合の処理を記述
      }
    }
    this.setState({ loading: false });
  }

  render() {
    const { loading, user } = this.state;
    const style = { position: "fixed", top: 0, left: 0, width: "100%", height: "100%", display: "flex", justifyContent: "center", alignItems: "center" };
    if (loading) {
      return (
        <div style={style}>
          <ActivityIndicator size="large" />
        </div>
      );
    }
    return (
      <AppContext.Provider value={{ user }}>
        <BrowserRouter>
          {/*ここで各コンポーネントのルーティングを設定する*/}
        </BrowserRouter>
      </AppContext.Provider>
    );
  }
}

export default App;

まとめ

API通信を待っている間にLoading画面を表示する方法について説明しました。 背景でも言った通り、汎用的な仕様だと思うので参考にしていただければ幸いです。


本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。