eyeCatch

今回は前回からの引き続きで、Firebaseとの連携(Realtime Databaseからのデータ取得)について、記載していきたいと思います。

記事の前提として、Realtime Database上のデータの読み取りに対しては、セキュリティ観点からアクセス制限をかけています。 このため、事前にAuthenticationによるユーザ認証を済ませてからでないとデータへのアクセスができません。

Realtime Databaseへのアクセス制限およびAuthenticationによる認証方法については、以下をご参照ください。

今回想定するユースケースですが、ブラウザからのリクエスト契機で、Realtime Database上からデータを取得し、データを元にHTMLを描画して返すというシンプルなものです。この場合、処理の流れとしては

  1. Firebase SDKを初期化する
  2. Authenticationでユーザ認証状態のチェックを行う
  3. ユーザ認証完了後、Realtime Databaseから動的なデータを取得する
  4. getServerSidePropsを用いて、Databaseから取得した動的データをコンポーネントに渡す
  5. コンポーネント側で動的データを使ってHTMLを描画し、ブラウザに返す

となりそうですが、、、これだと残念ながらうまくいきません。

NGパターン

以下のサンプルコードで確認すると、2番目のところで処理が止まってしまいます。 どうやらgetServerSidePropsが動作する時点では、firebase SDKにおいてAutenticationの初期化が完了していないようです。 ちなみにRealtime Databaseに関してはこの時点でアクティブになっており、読み取りルールでユーザ認証を不要とすればデータは読み取れます。

// 1. Firebase SDKを初期化する
if(!firebase.apps.length) {
  firebase.initializeApp(config);
}

function fetchRealtimeDatabase() {
  return new Promise((resolve) => {
    // 2. Authenticationでユーザ認証状態のチェックを行う
    firebase.auth().onAuthStateChanged((user) => {
      // 3. ユーザ認証完了後、Realtime Databaseから動的なデータを取得する
      if(user) {
        const database = firebase.database()
        database.ref(`products/${user.uid}`).once('value').then(snapshot => {
          resolve(snapshot.val())
        })
      }
    })
  })
}

export async function getServerSideProps() {
  const fetchData = await fetchRealtimeDatabase()
  //4. getServerSidePropsを用いて、Databaseから取得した動的データをコンポーネントに渡す
  return {
    props: {
      fetchData: fetchData
    }
  }
}

export default function Home(props) {  
  const { fetchData } = props
  // 5. コンポーネント側で動的データを使ってHTMLを描画し、ブラウザに返す
  return (
    <div id="hoge">
      {props.hoge}
    </div>
  )
}

OKパターン

上記のように、HTMLを描画する前にRealtime Databaseからデータを取得しようとすると失敗してしまします。 そこで対策方針としては、先に静的なHTMLの描画を行った上で、後から動的データを取得し、取得したデータを用いてHTMLの一部を書き換えるようにします。 具体的にはuseEffectフックを使います。useEffectはHTMLの描画が終わってから(componentDidMount状態)処理が始まるため、動的データの取得とHTML書き換えはこの中に記述して行きます。

  1. Firebase SDKを初期化する
  2. 静的なHTMLを描画する
  3. Authenticationでユーザ認証状態のチェックを行う
  4. ユーザ認証完了後、Realtime Databaseからデータを取得する
  5. 取得したデータを元にHTMLを書き換える

対策後のサンプルコードを以下に示します。

// 1. Firebase SDKを初期化する
if(!firebase.apps.length) {
  firebase.initializeApp(config);
}

export default function Home() {  
  useEffect(() => {
    //3. Authenticationでユーザ認証状態のチェックを行う
    firebase.auth().onAuthStateChanged((user) => {
      if(user){
        try {
          const database = firebase.database()
          //4. ユーザ認証完了後、Realtime Databaseからデータを取得する
          database.ref(`products/${user.uid}/${key}`).once('value').then(snapshot => {
            const product = snapshot.val()
            //5. 取得したデータを元にHTMLを書き換える
            if(product){            
              const hoge = document.getElementById('hoge')
              // elementが取得できた場合、動的な値で書き換える
              if(hoge){
                hoge.innerText = product.hoge
              }
            }
          })
        } catch(error){
          console.log(error)
        }
      }
    })
  })
  //2. 静的なHTMLを描画する
  return (
    <div id="hoge">
    </div>
  )
}

今回はページの読み込み直後では、firebase SDKの一部の機能が非アクティブになる事象とその回避策について説明しました。Next+firebaseの構成でまだ使用していない機能もあるため、他にも同様のパターンがありそうですが、そのあたりについて確認次第、こちらに掲載していきたいと思います。