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.