React + Firebase: A Simple Context-based Authentication Provider
Tags: react, firebase, webdev, typescript
Published: 2021-05-29 15:23:38

This post showcases a quick and easy way to make a Firebase-authenticated user available throughout your React web app.

We are using here plain React with Typescript, and no extra state management library like Redux involved.

Firebase offers us to register a callback that gets called every time a user is authenticated or signed out to get notified about the current authentication situation.

```ts
import firebase from "firebase/app";

firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    console.log("authenticated", user);
  } else {
    console.log("signed out");
  }
});
```

We thus could implement a React component that is interested in the authenticated user quite straightforward like this:

```ts
import * as React from "react";
import firebase from "firebase/app";

function CurrentUser() {
  const [user, setUser] = React.useState<firebase.User | null>(null);

  React.useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged(setUser);
    return unsubscribe;
  }, []);

  return <div>{user?.displayName || "unauthenticated"}</div>;
}
```

Our React component facilitates `React.useEffect` to register the Firebase `onAuthStateChanged` callback once after it was mounted. The effect returns the unsubscribe callback from `onAuthStateChanged`, ensuring that we don't run in any memory leaks.

Additionally, we have a state for the current user which's setter happens to match the callback signature perfectly.

This works just fine if only a single component in your React app is interested in the authentication state. Duplicating the state and effect for each other component would be cumbersome.

But more importantly, this approach works only for permanent (not conditionally rendered) components in our app's render tree, since otherwise, they might miss the initial authentication state because `onAuthStateChanged` only notifies changes.

One way to tackle this is to provide the authentication state globally utilizing a React context and companion hook. Let's start with the context first:

```ts
// FirebaseAuthContext.tsx
import * as React from "react";
import firebase from "firebase/app";

type User = firebase.User | null;
type ContextState = { user: User };

const FirebaseAuthContext =
  React.createContext<ContextState | undefined>(undefined);

const FirebaseAuthProvider: React.FC = ({ children }) => {
  const [user, setUser] = React.useState<User>(null);
  const value = { user };

  React.useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged(setUser);
    return unsubscribe;
  }, []);

  return (
    <FirebaseAuthContext.Provider value={value}>
      {children}
    </FirebaseAuthContext.Provider>
  );
};

export { FirebaseAuthProvider };
```

Few things to note here:
- `User` is a type alias for the authenticated Firebase user returned by `onAuthStateChanged`. The callback is called with `null` if no user is authenticated.
- `ContextState` is a type alias for the value provided by our context `FirebaseAuthContext`.
- We do not expose `FirebaseAuthContext` directly. Instead we expose `FirebaseAuthProvider` which encapsulates `FirebaseAuthContext.Provider` and a `onAuthStateChanged` subscription. It's quite similar to the `CurrentUser` implementation above.

Now let's define a simple hook that gives components interested in the authenticated user access to it:

```ts
// FirebaseAuthContext.tsx
// ...

function useFirebaseAuth() {
  const context = React.useContext(FirebaseAuthContext);
  if (context === undefined) {
    throw new Error(
      "useFirebaseAuth must be used within a FirebaseAuthProvider"
    );
  }
  return context.user;
}

export { FirebaseAuthProvider, useFirebaseAuth };
```

Our hook `useFirebaseAuth` simply facilitates `React.useContext` to access the previously defined context. We explicitly check for `undefined` to catch possible misuses as early as possible.

`FirebaseAuthProvider` usually is instantiated only once in an App, typically near the root in order to give all components below the opportunity to access the user via `useFirebaseAuth`. Here's a simple (constrived) example:

```ts
// example.ts
import * as React from "react";
import { FirebaseAuthProvider, useFirebaseAuth } from "./FirebaseAuthContext";

// ...initialize firebase

function App() {
  return (
    <FirebaseAuthProvider>
      <UserName />
      <UserEmail />
    </FirebaseAuthProvider>
  );
}

function UserName() {
  const user = useFirebaseAuth();
  return <div>{user?.displayName || "unauthenticated"}</div>;
}

function UserEmail() {
  const user = useFirebaseAuth();
  return <div>{user?.email || "-"}</div>;
}
```

A few things to note:
- Firebase initialization is left out for the sake of brevity. You can check it out [here](https://firebase.google.com/docs/web/setup) if you haven't already.
- The hook can be used by any component below `FirebaseAuthProvider` regardless of nesting level.
- Every notification of `onAuthStateChange` triggers a re-render.
- If your app manages state with Redux or a similar library, you may be better off handling the authentication state there as well.

I've found this approach very simple to implement and apply. It is based on [Kent C. Dodds excellent blog post "How to use React Context effectively"](https://kentcdodds.com/blog/how-to-use-react-context-effectively). You should definitely go and check it out for a more detailed description and some more background info.

Thanks for reading 🤗

If you liked it and don't mind, give it a ❤️

Take care & happy coding 🙌



*Photo by <a href="https://unsplash.com/@markusspiske?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Markus Spiske</a> on <a href="https://unsplash.com/s/photos/authentication?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>*