reactfire icon indicating copy to clipboard operation
reactfire copied to clipboard

useFirestore still suspends when using preloadFirestore

Open dylangarcia opened this issue 4 years ago • 4 comments

Version info

React: ^17.0.2

Firebase: ^8.8.0

ReactFire: ^3.0.0-rc.2

Other (e.g. Node, browser, operating system) (if applicable):

Test case

Working on making a codesandbox repro but everything keeps freezing on me, but it's essentially:

// index.tsx

ReactDOM.render(
  <React.StrictMode>
    <React.Suspense fallback={'Loading...'}>
      <FirebaseAppProvider firebaseConfig={firebaseConfig} suspense>
        <Router>
          <App />
        </Router>
      </FirebaseAppProvider>
    </React.Suspense>
  </React.StrictMode>,
  document.getElementById('root'),
);

// app.tsx
const preloadSDKs = (firebaseApp) => {
  return Promise.all([
    preloadFirestore({ firebaseApp }),
    preloadAuth({ firebaseApp }),
  ]);
};

export default function App() {
  preloadSDKs(useFirebaseApp());

  const { data } = useSigninCheck();

  return (
    <Switch>
      <Route path="/" component={data.signedIn ? Dashboard : AuthForm} />
    </Switch>
  );
}

Dashboard has a Switch in it that renders two routes:

  • /items
  • /profile

The /items route is a list of items from useFirestore The /profile route renders nothing from firestore.

If I load the page directly to /items and refresh and go between routes, React only suspends once If I navigate to /, then navigate to /items, React suspends twice - once when the whole page loads, and then once when it navigates to /items while useFirestore loads. I'm preloading the SDKs before I even navigate to /items with preloadSDKs, but it's still suspending.

Any ideas?

dylangarcia avatar Jul 24 '21 01:07 dylangarcia

From what I understand about preloading the Firestore SDKs, when preloading Firestore, all you are doing is preemptively importing Firestore once, higher in the component tree, and suspending the application until that has completed. By preloading Firestore you are not begining any fetches of data from the Firestore database. The preload Firestore function does not have the ability to know which components will need certain queries unless you use something like preloadFirestoreDoc which just begins the fetch earlier, like preload Firestore.

So on your first suspense, reactfire is importing and setting up Firestore. Once that is complete, you can access pages like the profile page you mentioned, but as soon as you access the items component which has useFirestoreCollectionData (or similar), reactfire learns for the first time that it needs to create an observable for the collection data, which is why it only suspends once you access that page.

If you only want the page to suspend once, there are a few solutions you could try:

  • If you only need one document, there is a preload method for a single document which you can call at the same time as the preloadFirestore method.
  • You can try calling useFirestoreCollectionData at an earlier point in the component tree, and pass it down to the component as a prop or if the component is very nested, create a React Context.

The issue with both of those options is that they will always read those documents, even if the user never navigates to the items page.

I hope that this helps!

epodol avatar Jul 24 '21 05:07 epodol

@epodol thanks for the response; that makes a lot of sense! I noticed that the flash went away if I fetched the document on the home screen, but I would ideally like to avoid those reads if they don't navigate to the page that needs it 🤔

My Suspense-fu isn't up to date, but for some reason I thought that you could nest <Suspense /> elements and it would just go up to the closest one. If that was the case, I could just wrap the visual part of that route in it and handle it there instead of the whole page flashing.

dylangarcia avatar Jul 24 '21 15:07 dylangarcia

Glad that helped @dylangarcia! Here are two more things you can try to make it work a bit better (similar to what you just mentioned):

  • Add another <Suspense> directly wrapping the other component that needs it. This should prevent the entire page from showing a loading fallback, and only block out the loading component. This would preserve any nav bars or footers while the data is still bring fetched.
  • Make the fallback a skeleton component that follows the same layout as the regular component, but with vauge greyed out text instead. This would just make it load without the entire page changing once it finishes loading.

epodol avatar Jul 24 '21 20:07 epodol

Ah I had the lower layer <Suspense> in the wrong location - I put it above the nested route the skeleton of the app is still all good, thanks!

Next step is to figure out the best way to not make it flash when trying to load the login page 😄.

I appreciate the help!

dylangarcia avatar Jul 24 '21 21:07 dylangarcia