diff --git a/chakra-ui/app/clientContext.tsx b/chakra-ui/app/clientContext.tsx new file mode 100644 index 00000000..134fb929 --- /dev/null +++ b/chakra-ui/app/clientContext.tsx @@ -0,0 +1,9 @@ +import { createContext } from "react"; + +export interface ClientStyleContextData { + reset: () => void; +} + +export const ClientStyleContext = createContext( + null, +); diff --git a/chakra-ui/app/entry.client.tsx b/chakra-ui/app/entry.client.tsx index bb3edb2f..7a36e54c 100644 --- a/chakra-ui/app/entry.client.tsx +++ b/chakra-ui/app/entry.client.tsx @@ -1,28 +1,60 @@ import createEmotionCache from "@emotion/cache"; import { CacheProvider } from "@emotion/react"; import { RemixBrowser } from "@remix-run/react"; -import { startTransition, StrictMode } from "react"; -import { hydrateRoot } from "react-dom/client"; +import { startTransition, StrictMode, useCallback, useState } from "react"; +import { hydrate } from "react-dom"; +import { ClientStyleContext } from "./clientContext"; -const hydrate = () => { +function ClientCacheProvider({ children }: { children: React.ReactNode }) { + const [cache, setCache] = useState(createEmotionCache({ key: "css" })); + + const reset = useCallback(() => { + return setCache(createEmotionCache({ key: "css" })); + }, []); + + return ( + + {children} + + ); +} + +const hydration = () => { const emotionCache = createEmotionCache({ key: "css" }); startTransition(() => { - hydrateRoot( - document, + /* + why `hydrate`? When using `HydrateRoot`, the deferred data, that is fast, will flash on the screen to the fallback during hydration. + I've fixed flashing fast deferred data this by using `hydrate` instead and it works. + If you don't defer any data you may use `HydrateRoot` instead, since it's newer. + + You can change to `HydrateRoot`, to see the flash of "medium data" on index page. + The flash will be more noticable, when the app grows, and hydration takes longer. + + hydrateRoot( + document, + + + + + , + ); + */ + hydrate( - + - + , + document, ); }); }; if (typeof requestIdleCallback === "function") { - requestIdleCallback(hydrate); + requestIdleCallback(hydration); } else { // Safari doesn't support requestIdleCallback // https://caniuse.com/requestidlecallback - setTimeout(hydrate, 1); + setTimeout(hydration, 1); } diff --git a/chakra-ui/app/root.tsx b/chakra-ui/app/root.tsx index 6cbad662..41fdaf12 100644 --- a/chakra-ui/app/root.tsx +++ b/chakra-ui/app/root.tsx @@ -1,4 +1,4 @@ -import { ChakraProvider, Box, Heading } from "@chakra-ui/react"; +import { Box, ChakraProvider, Heading } from "@chakra-ui/react"; import type { MetaFunction } from "@remix-run/node"; import { Links, @@ -7,8 +7,9 @@ import { Outlet, Scripts, ScrollRestoration, - useCatch, } from "@remix-run/react"; +import { useContext, useLayoutEffect, useRef } from "react"; +import { ClientStyleContext } from "./clientContext"; export const meta: MetaFunction = () => ({ charset: "utf-8", @@ -22,6 +23,21 @@ function Document({ children: React.ReactNode; title?: string; }) { + const clientStyleData = useContext(ClientStyleContext); + const reinjectStylesRef = useRef(true); + + /* + We do `useLayoutEffect`, to render the emotion styles, before browser paints the screen. + And we want to make sure, we only do this once, when the component mounts. + */ + useLayoutEffect(() => { + if (!reinjectStylesRef.current) return; + + clientStyleData?.reset(); + + reinjectStylesRef.current = false; + }, []); + return ( @@ -51,23 +67,6 @@ export default function App() { ); } -// How ChakraProvider should be used on CatchBoundary -export function CatchBoundary() { - const caught = useCatch(); - - return ( - - - - - [CatchBoundary]: {caught.status} {caught.statusText} - - - - - ); -} - // How ChakraProvider should be used on ErrorBoundary export function ErrorBoundary({ error }: { error: Error }) { return ( diff --git a/chakra-ui/app/routes/_index.tsx b/chakra-ui/app/routes/_index.tsx index d6117692..3f1f7dd8 100644 --- a/chakra-ui/app/routes/_index.tsx +++ b/chakra-ui/app/routes/_index.tsx @@ -1,9 +1,47 @@ -import { Box } from "@chakra-ui/react"; +import { Box, Text } from "@chakra-ui/react"; +import { defer, type LoaderArgs } from "@remix-run/node"; +import { Await, useLoaderData } from "@remix-run/react"; +import { Suspense } from "react"; + +export async function loader(_: LoaderArgs) { + const slowData = new Promise((resolve) => { + setTimeout(() => { + resolve("slow data"); + }, 1000); + }); + + const mediumData = new Promise((resolve) => { + setTimeout(() => { + resolve("medium data"); + }, 150); + }); + + const instantData = "instant data"; + + return defer({ + instantData, + mediumData, + slowData, + }); +} export default function Index() { + const { instantData, mediumData, slowData } = useLoaderData(); + return ( Hello World! + {instantData} + Loading Medium data...} + > + {(data) => {data}} + + Loading Slow data...} + > + {(data) => {data}} + ); }