Error regarding firebase onSnapshot context on refresh in Next 14 app router application

27 Views Asked by At

I was wondering if anyone could chime in and help me figure out how to solve this issue I'm having. I am using Next 14 with app router. Any help would be greatly appreciated!

My app is set up in the following manner:

└── my-app/
    └── src/
        ├── app/
        │   ├── my-page/
        │   │   └── page.tsx
        │   ├── ParentProvider.tsx
        │   ├── layout.tsx
        │   └── page.tsx
        ├── lib/
        │   └── firebase.ts
        ├── context/
        │   └── StakeholderContext.tsx
        └── hooks/
            └── useStakeholdersData.tsx

At the moment, my context is pulling data onSnapshot from my firebase firestore database like so

// StakeholderContext.tsx

import React, {
  ReactNode,
  createContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import { collection, onSnapshot, query } from "firebase/firestore";
import { db } from "@/lib/firebase/firebase";
import { Stakeholder } from "@/app/dashboard/stakeholders/types";

type CollectionDataType = Stakeholder;

interface FirebaseDataContextValue {
  data: CollectionDataType[] | null;
  loading: boolean;
  error: Error | null;
}

export const FirebaseDataContext = createContext<
  FirebaseDataContextValue | undefined
>(undefined);

interface StakeholderProviderProps {
  children: ReactNode;
}

export function StakeholderProvider({
  children,
}: StakeholderProviderProps): JSX.Element {
  const [data, setData] = useState<CollectionDataType[] | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const q = query(collection(db, "stakeholders"));
    const unsubscribe = onSnapshot(
      q,
      (snapshot) => {
        const docs = snapshot.docs.map((doc) => ({
          ...doc.data(),
          id: doc.id,
        }));
        setData(docs as Stakeholder[]);
        setLoading(false);
      },
      (err) => {
        console.error("Error getting documents: ", err);
        setError(err);
        setLoading(false);
      }
    );

    // Cleanup subscription on unmount
    return () => unsubscribe();
  }, []);

  const value = useMemo(
    () => ({
      data,
      loading,
      error,
    }),
    [data, loading, error]
  );

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

This provider is imported into a ParentProvider file that is a client side component wrapping my app (this provider wraps {children} in my layout.tsx).

// ParentProvider.tsx

"use client";

import React from "react";

import { StakeholderProvider } from "@/context/StakeholderContext";
import { StyledEngineProvider } from "@mui/system";

export default function ParentProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <StyledEngineProvider injectFirst>
       <StakeholderProvider>
          {children}
       </StakeholderProvider>
    </StyledEngineProvider>
  );
}

I call this context using the following hook

// useStakeholdersData.tsx

import { FirebaseDataContext } from "@/context/StakeholderContext";
import { useContext } from "react";

export default function useStakeholdersData() {
  const context = useContext(FirebaseDataContext);
  if (context === undefined) {
    throw new Error(
      "useFirebaseData must be used within a StakeholderProvider",
    );
  }
  return context;
}

which is then called in a client side component using this

const { data } = useStakeholdersData();

The issue I'm having is that on inital page load into the root page, my data loads in as expected when I navigate to the page that is pulling the data. However, when I refresh the page, I get an error regarding the inability to read properties of null indicating that my data either didn't load in, or the component is trying to access the data prior to the context loading it in. This is the error message I get:

Unhandled Runtime Error
TypeError: Cannot read properties of null (reading '0')

Source
src/app/dashboard/stakeholders/page.tsx (35:17) @ Stakeholders

  33 | >([]);
  34 | const [selectedStakeholder, setSelectedStakeholder] = useState<Stakeholder>(
> 35 |   stakeholders[0]
     |               ^
  36 | );
  37 |
  38 | useEffect(() => {

I've tried null checking all of the areas where the data is being accessed and that results in my page rendering with no data (format/structure is there but all fields empty). I have also tried leveraging the loading state with a conditional return but my page ends up defaulting to the loading state return unless I go back to the root page and refresh. Ideally, I'm expecting that when I refresh the page, the data should load in as it does when I initally visit the page.

0

There are 0 best solutions below