Using Components to Bootstrap Data for your App

drewclem

Drew Clements

Posted on April 30, 2021

Using Components to Bootstrap Data for your App

Have you ever discovered something that just made stuff click? Something so game-changing that it brightened the colors in your world almost instantly? I found one of those recently, and I wanted to share it!

What I'm referring to here is a little "hack" you can use to bootstrap data for your application to help ensure the data is where it needs to be, when it needs to be there. I believe this is a common pattern, but I'm unsure of what that specific component is referred to as- so we'll call it the bootstrapping component.

On a zoomed-out level, this component sits in your component tree and quietly fetches data anytime the app hits a refresh, page re-direct-- all of those situations where you'd expect some data from your state to disappear.

How it works

Here's how the bootstrapping component works on a very basic level.

  1. It sits in the component tree
  2. Data is fetched/mutated/merged (whatever you need it to do)
  3. Data is placed into whatever state management tool you're using, making it available where needed.

This is obviously a very simplistic view of how it works, but if you had to elevator pitch this to someone, then this may be a good way to frame it.

Into the weeds

Now let's get into how you would build one of these. In this example, we're going to be looking at building it in a NextJS app that's set up with Firebase. This pattern is tool-agnostic and will work with a wide array of setups

We're jumping in at the point after Firebase is already set up in this project.

Creating the Component

We'll start by creating a component in the /store directory. Components are usually placed in a components/ folder, but this specific type of component doesn't care about what's being rendered, and in fact, doesn't render anything other than children passed to it- so we'll keep it in a different directory for clarity.

It's good practice to name these components similar to the data they'll be responsible for. We'll be using it to fetch user profile info in this case, so we'll call ours UserProfileProvider.

'store/UseProfileProvider.js'

import { useEffect } from 'react'

const UserProfileProvider = ({ children }) => {
  useEffect(() => {

  }, [])

  return <>{children}</>
}

export default UserProfileProvider
Enter fullscreen mode Exit fullscreen mode

We'll need a useEffect later, so we'll go ahead and place an empty one for now

Placing the Component

Now that we have our component setup, let's place it in the component tree. We can focus on our bootstrapping component and the data it needs to handle once that is done.

Let's jump over to our _app.js in the /pages directory. You'll see we already have an AuthProvider component. This component is doing something similar to what we're building here, but it's specifically handling user auth- which can get complex quickly. That's an article for another day.

So, in our _app.js You'll see there's already a component tree forming from different pieces needed for the app. We're going to place our UseProfileProvider component as high up as we can, but within the AuthProvider component.

'pages/_app.js'

return (
    <AuthProvider>
      <UserProfileProvider />
      <GlobalLayout>
        <Component {...pageProps} />
      </GlobalLayout>
    </AuthProvider>
  )
}

export default MyApp
Enter fullscreen mode Exit fullscreen mode

Ignore the GlobalLayout component. It's a UI focused component that ensures each page has the same header and footer

Now our bootstrapping component is in place and we can start making it do the thing with the stuff.

Wiring it up

Our bootstrapping component is going to be fetching user profile information whenever there is a logged-in user.

Based on that last sentence, we know that we only want this to run when we have a logged-in user. In this case, we have a currentUser hook available from our AuthProvider that will allow us to check for that.

Jumping back to our UserProfileProvider, we're going to import firebase and useAuth, as those are two things we'll need to make this work.

'store/UseProfileProvider.js'

import { useEffect } from 'react'
import { useAuth } from 'store/AuthContext'
import 'firebase/firestore'

const UserProfileProvider = ({ children }) => {
  const { currentUser } = useAuth()

  useEffect(() => {

  }, [])

  return <>{children}</>
}

export default UserProfileProvider
Enter fullscreen mode Exit fullscreen mode

These imports have more going on in them, but at a basic level- useAuth is allowing us to check our AuthProvider for a currentUser, and firebase/firestore is providing the ability to call/read from our Firebase firestore.

From this point, we're going to move into our useEffect and write some logic for fetching the data we need.

The first thing we're going to do is write a function that will call firebase and return the profile info of our logged-in user.

One thing to note here is that we have a unique id of our current user available from the AuthProvider. We'll use that to make sure we're getting data from the right profile.

'store/UsesProfileProvider'

useEffect(() => {
    async function fetchUserProfile() {
      const userProfileInfo = await firebase.firestore()
        .collection('users')
        .doc(currentUser.userUid)
        .get()
    }
}, [])
Enter fullscreen mode Exit fullscreen mode

What this function is doing is calling firestore and saying "hey, I need the data from the 'users' collection where the name of the document is matching this userUid".

Another thing worth noting is that Firebase is promise-based, so you'll either be using async/await or .then() to resolve your promises

We're not quite done yet!

This code will error if it were called without a currentUser. It would error because it would be asking Firestore for data based on this userUid, but if there's no user then there's no userUid- so Firestore would come back with an error essentially saying "you gave me nothing, I can't work with that."

The fix here is to wrap where we call this function inside of our useEffect in an if statement. It would look something like this.

'store/UsesProfileProvider'

useEffect(() => {
    async function fetchUserProfile() {
      const userProfileInfo = await firebase.firestore()
        .collection('users')
        .doc(currentUser.userUid)
        .get()

      setProfileInfo(userProfileInfo.data())
    }

    if (currentUser) {
      fetchUserProfile()
    }
}, [currentUser])
Enter fullscreen mode Exit fullscreen mode

Now our fetchUserProfile function will only run when we have a logged-in user.

Notice that we also added currentUser to the dependency array at the end of our useEffect. This means that this code will run anytime the app boots up, whether it be from a page refresh, routing, or other scenarios, and also anytime data within our currentUser changes.

So, if our user logs in with another account, this code will run and give us fresh data to work with!

Ready for Use

Now that we have this bootstrapping component set up, we will always have fresh and relevant user profile info to work with!

Summary

I was recently shown this pattern and it instantly solved a problem I'd be fighting for a bit. I needed data to be available at first load, but to also update if anything with our current user changes, and to be returned on time for our users.

Anyways, I found this pattern extremely helpful to learn and it was also fun to build.

I hope whoever finds this article has the same takeaway!

Until next time!

💖 💪 🙅 🚩
drewclem
Drew Clements

Posted on April 30, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related