From 1d1027fab4df49f6999cdf5e360e1078d9328b7d Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Sat, 15 Nov 2025 12:22:30 +0330 Subject: [PATCH] fix: add null checks for document and firebase messaging to prevent SSR errors Added defensive programming by adding null checks for document object before accessing its properties and methods across multiple components (MultiSelect, ConfirmationModal, Dropdown, Modal). Also implemented lazy initialization for Firebase messaging to prevent SSR-related errors and ensure messaging is only initialized on the client side. These changes improve server-side rendering compatibility and prevent potential runtime errors. --- src/components/FormElements/MultiSelect.tsx | 3 +- src/components/Layouts/notification.tsx | 15 ++++++---- src/components/ui/ConfirmationModal.tsx | 6 ++-- src/components/ui/dropdown.tsx | 4 +-- src/components/ui/modal.tsx | 6 ++-- src/services/firebase.ts | 33 +++++++++++++++++++-- 6 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/components/FormElements/MultiSelect.tsx b/src/components/FormElements/MultiSelect.tsx index 5c645db..a34bf1e 100644 --- a/src/components/FormElements/MultiSelect.tsx +++ b/src/components/FormElements/MultiSelect.tsx @@ -130,8 +130,9 @@ function MultiSelect({ return; setShow(false); }; + document?.addEventListener("click", clickHandler); - return () => document.removeEventListener("click", clickHandler); + return () => document?.removeEventListener("click", clickHandler); }, [show]); return ( diff --git a/src/components/Layouts/notification.tsx b/src/components/Layouts/notification.tsx index 4baa996..0c0beee 100644 --- a/src/components/Layouts/notification.tsx +++ b/src/components/Layouts/notification.tsx @@ -8,12 +8,15 @@ import { toast } from "react-toastify"; const Notification = ({ children }: PropsWithChildren) => { useEffect(() => { - onMessage(messaging, (payload) => { - console.log("Message received. ", payload); - if (payload.notification) { - toast.success(payload.notification.title ?? "New Notification"); - } - }); + const messagingInstance = messaging(); + if (messagingInstance) { + onMessage(messagingInstance, (payload) => { + console.log("Message received. ", payload); + if (payload.notification) { + toast.success(payload.notification.title ?? "New Notification"); + } + }); + } }, []); return <>{children}; diff --git a/src/components/ui/ConfirmationModal.tsx b/src/components/ui/ConfirmationModal.tsx index 25fa6ce..38b9a8e 100644 --- a/src/components/ui/ConfirmationModal.tsx +++ b/src/components/ui/ConfirmationModal.tsx @@ -54,12 +54,12 @@ const ConfirmationModal: React.FC = (props) => { if (isOpen) { document?.addEventListener("keydown", handleEscape); - document.body.style.overflow = "hidden"; + if (document) document.body.style.overflow = "hidden"; } return () => { - document.removeEventListener("keydown", handleEscape); - document.body.style.overflow = "unset"; + document?.removeEventListener("keydown", handleEscape); + if (document) document.body.style.overflow = "unset"; }; }, [isOpen, onCancel]); diff --git a/src/components/ui/dropdown.tsx b/src/components/ui/dropdown.tsx index ff4fcc5..47d4c8e 100644 --- a/src/components/ui/dropdown.tsx +++ b/src/components/ui/dropdown.tsx @@ -46,9 +46,9 @@ export function Dropdown({ children, isOpen, setIsOpen }: DropdownProps) { if (isOpen) { triggerRef.current = document.activeElement as HTMLElement; - document.body.style.pointerEvents = "none"; + if (document) document.body.style.pointerEvents = "none"; } else { - document.body.style.removeProperty("pointer-events"); + if (document) document.body.style.removeProperty("pointer-events"); setTimeout(() => { triggerRef.current?.focus(); diff --git a/src/components/ui/modal.tsx b/src/components/ui/modal.tsx index e065c6b..df5c38c 100644 --- a/src/components/ui/modal.tsx +++ b/src/components/ui/modal.tsx @@ -33,13 +33,13 @@ const Modal: React.FC = ({ useEffect(() => { if (isOpen) { - document.body.style.overflow = "hidden"; + if (document) document.body.style.overflow = "hidden"; } else { - document.body.style.overflow = "unset"; + if (document) document.body.style.overflow = "unset"; } return () => { - document.body.style.overflow = "unset"; + if (document) document.body.style.overflow = "unset"; }; }, [isOpen]); diff --git a/src/services/firebase.ts b/src/services/firebase.ts index 24c4c77..92abc78 100644 --- a/src/services/firebase.ts +++ b/src/services/firebase.ts @@ -2,6 +2,7 @@ import { COOKIE_KEYS } from "@/lib/shared/cookies"; import { initializeApp } from "firebase/app"; import { getMessaging, getToken } from "firebase/messaging"; import Cookies from "js-cookie"; + const firebaseConfig = { apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, @@ -11,11 +12,35 @@ const firebaseConfig = { appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, }; +// Initialize Firebase app const app = initializeApp(firebaseConfig); -export const messaging = getMessaging(app); + +// Lazy initialization of messaging to avoid SSR issues +let messaging: any = null; + +const getMessagingInstance = () => { + if (typeof window !== 'undefined' && !messaging) { + try { + messaging = getMessaging(app); + } catch (error) { + console.warn('Failed to initialize Firebase Messaging:', error); + } + } + return messaging; +}; export const requestForToken = () => { - return getToken(messaging) + // Only run on client side + if (typeof window === 'undefined') { + return Promise.resolve(null); + } + + const messagingInstance = getMessagingInstance(); + if (!messagingInstance) { + return Promise.resolve(null); + } + + return getToken(messagingInstance) .then((currentToken) => { if (currentToken) { console.log("current token for client: ", currentToken); @@ -27,9 +52,13 @@ export const requestForToken = () => { ); // ... } + return currentToken; }) .catch((err) => { console.log("An error occurred while retrieving token. ", err); // ... + return null; }); }; + +export { getMessagingInstance as messaging };