diff --git a/packages/lib/hooks/useMountedState.ts b/packages/lib/hooks/useMountedState.ts new file mode 100644 index 0000000000..70597ea4d9 --- /dev/null +++ b/packages/lib/hooks/useMountedState.ts @@ -0,0 +1,22 @@ +import { useCallback, useEffect, useRef } from "react"; + +// credits: https://github.com/streamich/react-use/blob/master/src/useMountedState.ts +// jsdoc generated by chat-gpt :) +/** + * A custom React hook that returns a function to check if the component is mounted. + * @returns {function(): boolean} A function that returns `true` if the component is mounted and `false` otherwise. + */ +export default function useMountedState(): () => boolean { + const mountedRef = useRef(false); + const get = useCallback(() => mountedRef.current, []); + + useEffect(() => { + mountedRef.current = true; + + return () => { + mountedRef.current = false; + }; + }, []); + + return get; +} diff --git a/packages/lib/hooks/useTheme.tsx b/packages/lib/hooks/useTheme.tsx index ed9e29c0dc..759fe7fbbc 100644 --- a/packages/lib/hooks/useTheme.tsx +++ b/packages/lib/hooks/useTheme.tsx @@ -1,29 +1,33 @@ import { useTheme as useNextTheme } from "next-themes"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useEmbedTheme } from "@calcom/embed-core/embed-iframe"; import type { Maybe } from "@calcom/trpc/server"; +import useMountedState from "./useMountedState"; + // makes sure the ui doesn't flash export default function useTheme(theme?: Maybe) { - theme = theme || "system"; - const { resolvedTheme, setTheme, forcedTheme } = useNextTheme(); - const [isReady, setIsReady] = useState(false); + let currentTheme: Maybe = theme || "system"; + + const { resolvedTheme, setTheme, forcedTheme, theme: activeTheme } = useNextTheme(); const embedTheme = useEmbedTheme(); + const isMounted = useMountedState(); // Embed UI configuration takes more precedence over App Configuration - theme = embedTheme || theme; + currentTheme = embedTheme || theme; useEffect(() => { - if (theme) { - setTheme(theme); + if (currentTheme !== activeTheme && typeof currentTheme === "string") { + setTheme(currentTheme); } - setIsReady(true); - }, [theme, setTheme]); + // eslint-disable-next-line react-hooks/exhaustive-deps -- we do not want activeTheme to re-render this effect + }, [currentTheme, setTheme]); return { resolvedTheme, setTheme, - isReady, forcedTheme, + activeTheme, + isReady: isMounted(), }; }