Gatsby: How to use hooks inside wrapRootElement?

Created on 5 Apr 2020  路  3Comments  路  Source: gatsbyjs/gatsby

Summary

I assume that exports.wrapRootElement in "gatsby-browser.js" is meant to be a component that accepts another component and well... wraps it.

As such, I assume I could use hooks inside this component. Take a look at this simplified example of my "gatsby-browser.js":

// Assigning the function directly to wrapRootElement causes a react hooks
// linter error because it thinks wrapRootElement is not a function component.
// Probably because the name starts with a lowercase letter?
const Wrapper = ({ element }) => {
  const [loggedIn, setLoggedIn] = useState(/* some code */)
  useEffect(() => {
    firebase.auth().onAuthStateChanged(user => {
      setLoggedIn(!!user)
    })
  })
  return (
    <FirebaseContext.Provider value={{ loggedIn }}>
      {element}
    </FirebaseContext.Provider>
  )
}

exports.wrapRootElement = Wrapper 

This throws the following error:

Unhandled Rejection (Error): Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

question or discussion

Most helpful comment

Off the top of my head try this:

const FireBaseApp = () => {
const [loggedIn, setLoggedIn] = useState(/* some code */)
  useEffect(() => {
    firebase.auth().onAuthStateChanged(user => {
      setLoggedIn(!!user)
    })
  })
  return (
    <FirebaseContext.Provider value={{ loggedIn }}>
      {element}
    </FirebaseContext.Provider>
  )
}

const Wrapper = ({ element }) => {
  return <FireBaseApp element={element} />
}

exports.wrapRootElement = Wrapper 

So we're just wrapping everything in a fragment so that it is now a component. I think that should work

All 3 comments

Off the top of my head try this:

const FireBaseApp = () => {
const [loggedIn, setLoggedIn] = useState(/* some code */)
  useEffect(() => {
    firebase.auth().onAuthStateChanged(user => {
      setLoggedIn(!!user)
    })
  })
  return (
    <FirebaseContext.Provider value={{ loggedIn }}>
      {element}
    </FirebaseContext.Provider>
  )
}

const Wrapper = ({ element }) => {
  return <FireBaseApp element={element} />
}

exports.wrapRootElement = Wrapper 

So we're just wrapping everything in a fragment so that it is now a component. I think that should work

Thanks, this worked! I'm a bit surprised, because I thought a functional component in React was just a function that returns JSX, but I guess it also depends on the way it is called behind the scenes?

Also for future reference, so I can debug these things on my own. What's the easiest way to find where wrapRootElement is in the source code? Do I just need to grep the codebase?

@vedantroy wrapRootElement is here.

If you look at the way the apiRunner is called, you'll see why it doesn't work using hooks inside of wrapRootElement. It has to be a react element that contains the hook, and that's what you're doing by wrapping it with another React Functional Component.

I actually just got done talking about this here, the other day.

Was this page helpful?
0 / 5 - 0 ratings