Skip to content

Latest commit

 

History

History
318 lines (240 loc) · 10.4 KB

use.md

File metadata and controls

318 lines (240 loc) · 10.4 KB

Using ReactFirebase

Access your firebase app from any component

Since ReactFirebase uses React's Context API, any component under a FirebaseAppProvider can use useFirebaseApp() to get your initialized app. Plus, all ReactFirebase hooks will automatically check context to see if a firebase app is available.

// ** INDEX.JS **
const firebaseConfig = {
  /* add your config object from the Firebase console */
};

render(
  <FirebaseAppProvider firebaseConfig={firebaseConfig}>
    <MyApp />
  </FirebaseAppProvider>
);

// ** MYCOMPONENT.JS **

function MyComponent(props) {
  // useFirestore will get the firebase app from Context!
  const documentReference = useFirestore()
    .collection('burritos')
    .doc('vegetarian');

  // ...
}

Access the current user

The useUser() hook returns the currently signed-in user. Like the other ReactFirebase Hooks, you need to wrap it in Suspense or provide a startWithValue.

function HomePage(props) {
  // no need to use useFirebaseApp - useUser calls it under the hood
  const user = useUser();

  return <h1>Welcome Back {user.displayName}!</h1>;
}

Note: useUser will also automatically lazily import the firebase/auth SDK if it has not been imported already.

Decide what to render based on a user's auth state

The AuthCheck component makes it easy to hide/show UI elements based on a user's auth state. It will render its children if a user is signed in, but if they are not signed in, it renders its fallback prop:

render(
  <AuthCheck fallback={<LoginPage />}>
    <HomePage />
  </AuthCheck>
);

Log Page Views to Google Analytics for Firebase with React Router

import { useAnalytics } from '@protrex/react-firebase';
import { Router, Route, Switch } from 'react-router';

function MyPageViewLogger({ location }) {
  const analytics = useAnalytics();

  // By passing `location.pathname` to the second argument of `useEffect`,
  // we only log on first render and when the `pathname` changes
  useEffect(() => {
    analytics.logEvent('page-view', { path_name: location.pathname });
  }, [location.pathname]);

  return null;
}

function App() {
  const analytics = useAnalytics();

  return (
    <Router>
      <Switch>
        <Route exact path="/about" component={<AboutPage />} />
        <Route component={<NotFoundPage />} />
      </Switch>
      <MyPageViewLogger />
    </Router>
  );
}

Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card

import {
  AuthCheck,
  StorageImage,
  useFirestoreDocData,
  useUser,
  useAuth,
  useFirestore
} from '@protrex/react-firebase';

const DEFAULT_IMAGE_PATH = 'userPhotos/default.jpg';

function ProfileCard() {
  // get the current user.
  // this is safe because we've wrapped this component in an `AuthCheck` component.
  const user = useUser();

  // read the user details from Firestore based on the current user's ID
  const userDetailsRef = useFirestore()
    .collection('users')
    .doc(user.uid);

  let { commonName, favoriteAnimal, profileImagePath } = useFirestoreDocData(
    userDetailsRef
  );

  // defend against null field(s)
  profileImagePath = profileImagePath || DEFAULT_IMAGE_PATH;

  if (!commonName || !favoriteAnimal) {
    throw new Error(MissingProfileInfoError);
  }

  return (
    <div>
      <h1>{commonName}</h1>
      {/*
        `StorageImage` converts a Cloud Storage path into a download URL and then renders an image
       */}
      <StorageImage style={{ width: '100%' }} storagePath={profileImagePath} />
      <span>Your favorite animal is the {favoriteAnimal}</span>
    </div>
  );
}

function LogInForm() {
  const auth = useAuth();

  const signIn = () => {
    auth.signInWithEmailAndPassword(email, password);
  };

  return <MySignInForm onSubmit={signIn} />;
}

function ProfilePage() {
  return (
    {/*
      Render a spinner until components are ready
    */}
    <Suspense fallback={<MyLoadingSpinner />}>
      {/*
        Render `ProfileCard` only if a user is signed in.
        Otherwise, render `LoginForm`
       */}
      <AuthCheck fallback={<LogInForm />}>{ProfileCard}</AuthCheck>
    </Suspense>
  );
}

Manage Loading States

ReactFirebase is designed to integrate with React's Suspense API, but also supports use cases where Suspense isn't needed or wanted.

Default: Suspense

Say we have a component called Burrito that uses useFirestoreDoc:

function Burrito() {
  const firebaseApp = useFirestore();
  const burritoRef = firestore()
    .collection('tryreactfirebase')
    .doc('burrito');

  // subscribe to the doc. just one line!
  // throws a Promise for Suspense to catch,
  // and then streams live updates
  const burritoDoc = useFirestoreDoc(burritoRef);

  const isYummy = burritoDoc.data().yummy;

  return <p>The burrito is {isYummy ? 'good' : 'bad'}!</p>;
}

The parent component of Burrito can use Suspense to render a fallback component until useFirestoreDoc returns a value:

function FoodRatings() {
  return (
    <Suspense fallback={'loading burrito status...'}>
      <Burrito />
    </Suspense>
  );
}

Bonus: SuspenseWithPerf

ReactFirebase provides an a wrapper around Suspense called SuspenseWithPerf that instruments your Suspense loads with a Firebase Performance Monitoring custom trace. It looks like this:

function FoodRatings() {
  return (
    <SuspenseWithPerf fallback={'loading burrito status...'} traceId={'load-burrito-status'}>
      <Burrito />
    </SuspenseWithPerf>
  );
}

Don't want Suspense? Provide an initial value

What if we don't want to use Suspense, or we're server rendering and we know what the initial value should be? In that case we can provide an initial value to any ReactFirebase hook:

function Burrito() {
  const firebaseApp = useFirebaseApp();
  const burritoRef = firebaseApp
    .firestore()
    .collection('tryreactfirebase')
    .doc('burrito');

  // subscribe to the doc. just one line!
  // returns the `startWithValue`,
  // and then streams live updates
  const burritoDoc = useFirestoreDocData(burritoRef, {
    startWithValue: {
      yummy: true
    }
  });

  const isYummy = burritoDoc.data().yummy;

  return <p>The burrito is {isYummy ? 'good' : 'bad'}!</p>;
}

The parent component of Burrito now doesn't need to use Suspense:

function FoodRatings() {
  return <Burrito />;
}

Solve Warning: App triggered a user-blocking update that suspended. with useTransition

This warning can be solved with React's useTransition hook. Check out the sample code's Firestore example to see how to use this with ReactFirebase:

const FavoriteAnimals = props => {
const firestore = useFirestore();
const baseRef = firestore.collection('animals');
const [isAscending, setIsAscending] = useState(true);
const query = baseRef.orderBy('commonName', isAscending ? 'asc' : 'desc');
const [startTransition, isPending] = useTransition({
timeoutMs: 1000
});
const toggleSort = () => {
startTransition(() => {
setIsAscending(!isAscending);
});
};
const addNewAnimal = commonName =>
baseRef.add({
commonName
});
const removeAnimal = id => baseRef.doc(id).delete();
return (
<>
<AnimalEntry saveAnimal={addNewAnimal} />
<br />
<button onClick={toggleSort} disabled={isPending}>
Sort {isAscending ? '^' : 'v'}
</button>
<React.Suspense fallback="loading...">
<List query={query} removeAnimal={removeAnimal} />
</React.Suspense>
</>
);
};

Lazy Load the Firebase SDKs

Including the Firebase SDKs in your main JS bundle (by using import 'firebase/firestore', for example) will increase your bundle size. To get around this, you can lazy load the Firebase SDK with ReactFirebase. As long as a component has a parent that is a FirebaseAppProvider, you can use an SDK hook (useFirestore, useDatabase, useAuth, useStorage) like so:

MyComponent.jsx

import React from 'react';
// WE ARE NOT IMPORTING THE FIRESTORE SDK UP HERE
import { useFirestoreDocData, useFirestore } from '@protrex/react-firebase';

export function MyComponent(props) {
  // automatically lazy loads the Cloud Firestore SDK
  const firestore = useFirestore();

  const ref = firestore().doc('count/counter');
  const data = useFirestoreDocData(ref);

  return <h1>{data.value}</h1>;
}

The render-as-you-fetch pattern

The React docs recommend kicking off reads as early as possible in order to reduce perceived load times. ReactFirebase offers a number of preload methods to help you do this.

Preload an SDK

Call preloadFirestore (or preloadAuth, preloadRemoteConfig, etc) to start fetching a Firebase library in the background. Later, when you call useFirestore in a component, the useFirestore hook may not need to suspend if the preload has already completed.

Initialize an SDK

Some Firestore SDKs need to be initialized (firebase.remoteConfig().fetchAndActivate()), or need to have settings set before any other calls are made (firebase.firestore().enablePersistence()). This can be done by passing a function returning a promise to the setup option.

preloadFirestore({
  setup: firestore => firestore().enablePersistence()
});

Preload Data

ReactFirebase's data fetching hooks don't fully support preloading yet. The experimental preloadFirestoreDoc function allows you to subscribe to a Firestore document if you know you call useFirestoreDoc somewhere farther down the component tree.

Advanced: Using RxJS observables to combine multiple data sources

All ReactFirebase hooks are powered by useObservable. By calling useObservable directly, you can subscribe to any observable in the same manner as the built-in ReactFirebase hooks. If you use RxFire and useObservable together, you can accomplish more advanced read patterns (like OR queries in Firestore!).