Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v2] Cannot pass initializing Firebase app when building static HTML #6386

Closed
ng-hai opened this issue Jul 11, 2018 · 4 comments
Closed

[v2] Cannot pass initializing Firebase app when building static HTML #6386

ng-hai opened this issue Jul 11, 2018 · 4 comments

Comments

@ng-hai
Copy link
Contributor

@ng-hai ng-hai commented Jul 11, 2018

Description

I'm building an app integrated with Firebase, and implement a module like this

import firebase from 'firebase/app'
import 'firebase/auth'

const config = {
  apiKey: 'XXXXX',
  authDomain: 'XXXXX.firebaseapp.com',
  databaseURL: 'https://XXXXX.firebaseio.com',
  projectId: 'XXXXX',
  storageBucket: 'XXXXX.appspot.com',
  messagingSenderId: 'XXXXX',
}

class Firebase {
  constructor () {
    firebase.initializeApp(config)
    this.auth = firebase.auth()
  }
}

export default new Firebase()

There is no error when running Gatsby in development mode, but it appears when running Gatsby build at Building static HTML for pages step. It says:

Error: The XMLHttpRequest compatibility library was not found.

Steps to reproduce

Run gatsby build

Expected result

Pass the Building static HTML for pages step.

Actual result

Get error:

Error: The XMLHttpRequest compatibility library was not found.

Environment

System:
  OS: macOS High Sierra 10.13.5
  CPU: x64 Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
  Shell: 5.3 - /bin/zsh
Binaries:
  Node: 8.11.1 - /usr/local/bin/node
  Yarn: 1.7.0 - /usr/local/bin/yarn
  npm: 5.6.0 - /usr/local/bin/npm
Browsers:
  Chrome: 67.0.3396.99
  Safari: 11.1.1
npmPackages:
  gatsby: next => 2.0.0-beta.22 
  gatsby-plugin-no-sourcemaps: next => 2.0.0-beta.2 
  gatsby-plugin-react-helmet: next => 3.0.0-beta.3 
  gatsby-plugin-styled-components: next => 3.0.0-beta.2 
  gatsby-source-filesystem: ^1.5.39 => 1.5.39 
  gatsby-transformer-remark: ^1.7.44 => 1.7.44 

File contents (if changed)

gatsby-config.js:

module.exports = {
  siteMetadata: {
    title: 'Simple Trello',
  },
  plugins: [
    'gatsby-plugin-react-helmet',
    'gatsby-plugin-styled-components',
    'gatsby-plugin-no-sourcemaps',
    'gatsby-transformer-remark',
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        name: 'pages',
        path: `${__dirname}/src/pages/`,
      },
    },
  ],
}

package.json:

{
  "dependencies": {
    "firebase": "^5.2.0"
  }
}

gatsby-node.js:

const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')

exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions

  return new Promise(resolve => {
    createPage({
      path: '/',
      matchPath: '/:path',
      component: path.resolve('./src/app.js'),
    })

    graphql(`
      {
        allMarkdownRemark {
          edges {
            node {
              fields {
                slug
              }
            }
          }
        }
      }
    `).then(result => {
      result.data.allMarkdownRemark.edges.forEach(({ node }) => {
        createPage({
          path: node.fields.slug,
          component: path.resolve('./src/templates/blog.js'),
        })
      })
      resolve()
    })
  })
}

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === 'MarkdownRemark') {
    const slug = createFilePath({
      node,
      getNode,
      basePath: 'pages',
      trailingSlash: false,
    })

    createNodeField({
      node,
      name: 'slug',
      value: slug,
    })
  }
}

gatsby-browser.js: N/A
gatsby-ssr.js: N/A

How I fix temporary

I wrap the Firebase initializing with checking window first and it passes the build step

class Firebase {
  constructor () {
    if (typeof window !== 'undefined') {
      firebase.initializeApp(config)
      this.auth = firebase.auth()
    }
  }
}

Is there any better workaround?

@dannywils
Copy link
Contributor

@dannywils dannywils commented Jul 11, 2018

This is expected because window is not defined during build. See reason 1 in https://www.gatsbyjs.org/docs/debugging-html-builds/

Your workaround seems fine to me. Alternatively you could use export the class and only instantiate in the componentDidMount() lifecycle method which only executes on the client where window is available.

You may also want to use the singleton pattern to ensure you reuse the same instance throughout your app.

Example:

class Firebase {
  static instance;
  constructor() {
    if (instance) {
      return instance;
    }
    firebase.initializeApp(config);
    this.auth = firebase.auth();
    this.instance = this;
  }
}

export default Firebase;

and then used in your components:

import Firebase from './firebase';

class MyComponent extends React.Component {
  componentDidMount() {
    this.firebase = new Firebase();
  }

  render() {
    // ...
  }
}

@ng-hai
Copy link
Contributor Author

@ng-hai ng-hai commented Jul 11, 2018

Thanks, @dannywils, in my case I want to use Firebase outside components, like initialize default context state

import Firebase from './firebase'

const initialState = {
  signIn: Boolean(Firebase.auth.currentUser)
}

const { Provider, Consumer } = React.createContext(initialState)

class AuthContext extends React.PureComponent {
  // ...
}

@dannywils
Copy link
Contributor

@dannywils dannywils commented Jul 11, 2018

In that case your workaround is perfectly fine. If you want to move the window check outside of your class, you can move it to where you use Firebase for the first time.

import Firebase from './firebase';

let initialState = { signIn: false };

if (typeof window !== 'undefined') {
  const firebase = new Firebase();
  initialState = Boolean(firebase.auth.currentUser);
}

const { Provider, Consumer } = React.createContext(initialState);

class AuthContext extends React.PureComponent {
  // ...
}

@vedantroy
Copy link

@vedantroy vedantroy commented Apr 5, 2020

I think a good solution to the problem is:

  1. use wrapRootElement in "gatsby-browser.js" to wrap your entire app in Firebase. You can just pass the Firebase instance directly to your app (as a prop), or, you can use the React Context api.
  2. If you are using the React Context api, in "gatsby-browser.js" like this:
// gatsby-browser.js
import { navigate } from 'gatsby'
import React, {useState, useEffect} from 'react'
import firebase from 'firebase/app'
import "firebase/auth"

import FirebaseContext from './src/firebase'

firebase.initializeApp({
  // https://www.gatsbyjs.org/docs/environment-variables/
  apiKey: process.env.apiKey,
  authDomain: process.env.authDomain,
  databaseURL: process.env.databaseURL,
  projectId: process.env.projectId,
  storageBucket: process.env.storageBucket,
  messagingSenderId: process.env.messagingSenderId,
  appId: process.env.appId,
})

const LOCAL_STORAGE_LOGIN_KEY = "user_is_logged_in"

// We cannot assign wrapRootElement to a React function component directly
// https://github.com/gatsbyjs/gatsby/issues/22833
const App = ({ root }) => {
  const [loggedIn, setLoggedIn] = useState(
    localStorage.getItem(LOCAL_STORAGE_LOGIN_KEY) === "true"
  )
  useEffect(() => {
    firebase.auth().onAuthStateChanged((user) => {
      const userLoggedIn = !!user
      localStorage.setItem(
        LOCAL_STORAGE_LOGIN_KEY,
        JSON.stringify(userLoggedIn)
      )
      setLoggedIn(userLoggedIn)
      if (!userLoggedIn) navigate("/login")
    })
  })
  return (
    <FirebaseContext.Provider value={{ firebase, loggedIn }}>
      {root}
    </FirebaseContext.Provider>
  )
}

export const wrapRootElement = ({ element }) => <App root={element} />

where firebase.ts is just:

import React from "react"
import type firebase from "firebase"

// unnamed default export
export default React.createContext<{loggedIn: boolean, firebase: typeof firebase}>({loggedIn: false, firebase: null})

Drop a react if this was helpful! 😀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants