diff --git a/app/components/CountrySelector.tsx b/app/components/CountrySelector.tsx index 358d678..a3bfeb0 100644 --- a/app/components/CountrySelector.tsx +++ b/app/components/CountrySelector.tsx @@ -1,4 +1,4 @@ -import {useFetcher, useLocation, useMatches} from '@remix-run/react'; +import {useFetcher, useLocation, useRouteLoaderData} from '@remix-run/react'; import {useCallback, useEffect, useRef} from 'react'; import {useInView} from 'react-intersection-observer'; import clsx from 'clsx'; @@ -10,12 +10,12 @@ import {Heading} from '~/components/Text'; import {IconCheck} from '~/components/Icon'; import type {Localizations, Locale} from '~/lib/type'; import {DEFAULT_LOCALE} from '~/lib/utils'; -import {useRootLoaderData} from '~/hooks/useRootLoaderData'; +import type {RootLoader} from '~/root'; export function CountrySelector() { const fetcher = useFetcher(); const closeRef = useRef(null); - const rootData = useRootLoaderData(); + const rootData = useRouteLoaderData('root'); const selectedLocale = rootData?.selectedLocale ?? DEFAULT_LOCALE; const {pathname, search} = useLocation(); const pathWithoutLocale = `${pathname.replace( diff --git a/app/components/Link.tsx b/app/components/Link.tsx index bd28406..50d0777 100644 --- a/app/components/Link.tsx +++ b/app/components/Link.tsx @@ -3,9 +3,10 @@ import { NavLink as RemixNavLink, type NavLinkProps as RemixNavLinkProps, type LinkProps as RemixLinkProps, + useRouteLoaderData, } from '@remix-run/react'; -import {useRootLoaderData} from '~/hooks/useRootLoaderData'; +import type {RootLoader} from '~/root'; type LinkProps = Omit & { className?: RemixNavLinkProps['className'] | RemixLinkProps['className']; @@ -28,7 +29,7 @@ type LinkProps = Omit & { */ export function Link(props: LinkProps) { const {to, className, ...resOfProps} = props; - const rootData = useRootLoaderData(); + const rootData = useRouteLoaderData('root'); const selectedLocale = rootData?.selectedLocale; let toWithLocale = to; diff --git a/app/components/Layout.tsx b/app/components/PageLayout.tsx similarity index 96% rename from app/components/Layout.tsx rename to app/components/PageLayout.tsx index 3b90e78..ee5b240 100644 --- a/app/components/Layout.tsx +++ b/app/components/PageLayout.tsx @@ -1,4 +1,4 @@ -import {useParams, Form, Await} from '@remix-run/react'; +import {useParams, Form, Await, useRouteLoaderData} from '@remix-run/react'; import useWindowScroll from 'react-use/esm/useWindowScroll'; import {Disclosure} from '@headlessui/react'; import {Suspense, useEffect, useMemo} from 'react'; @@ -27,7 +27,7 @@ import { } from '~/lib/utils'; import {useIsHydrated} from '~/hooks/useIsHydrated'; import {useCartFetchers} from '~/hooks/useCartFetchers'; -import {useRootLoaderData} from '~/hooks/useRootLoaderData'; +import type {RootLoader} from '~/root'; type LayoutProps = { children: React.ReactNode; @@ -37,7 +37,7 @@ type LayoutProps = { }; }; -export function Layout({children, layout}: LayoutProps) { +export function PageLayout({children, layout}: LayoutProps) { const {headerMenu, footerMenu} = layout || {}; return ( <> @@ -105,7 +105,8 @@ function Header({title, menu}: {title: string; menu?: EnhancedMenu}) { } function CartDrawer({isOpen, onClose}: {isOpen: boolean; onClose: () => void}) { - const rootData = useRootLoaderData(); + const rootData = useRouteLoaderData('root'); + if (!rootData) return null; return ( @@ -321,7 +322,7 @@ function DesktopHeader({ } function AccountLink({className}: {className?: string}) { - const rootData = useRootLoaderData(); + const rootData = useRouteLoaderData('root'); const isLoggedIn = rootData?.isLoggedIn; return ( @@ -342,7 +343,8 @@ function CartCount({ isHome: boolean; openCart: () => void; }) { - const rootData = useRootLoaderData(); + const rootData = useRouteLoaderData('root'); + if (!rootData) return null; return ( }> diff --git a/app/hooks/useRootLoaderData.tsx b/app/hooks/useRootLoaderData.tsx deleted file mode 100644 index 9e80fab..0000000 --- a/app/hooks/useRootLoaderData.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import {useMatches} from '@remix-run/react'; -import type {SerializeFrom} from '@shopify/remix-oxygen'; - -import type {loader} from '~/root'; - -/** - * Access the result of the root loader from a React component. - */ -export function useRootLoaderData() { - const [root] = useMatches(); - return root?.data as SerializeFrom; -} diff --git a/app/lib/utils.ts b/app/lib/utils.ts index a09a888..6891ced 100644 --- a/app/lib/utils.ts +++ b/app/lib/utils.ts @@ -1,4 +1,4 @@ -import {useLocation, useMatches} from '@remix-run/react'; +import {useLocation, useRouteLoaderData} from '@remix-run/react'; import type {MoneyV2} from '@shopify/hydrogen/storefront-api-types'; import type {FulfillmentStatus} from '@shopify/hydrogen/customer-account-api-types'; import typographicBase from 'typographic-base'; @@ -8,7 +8,7 @@ import type { MenuFragment, ParentMenuItemFragment, } from 'storefrontapi.generated'; -import {useRootLoaderData} from '~/hooks/useRootLoaderData'; +import type {RootLoader} from '~/root'; import {countries} from '~/data/countries'; import type {I18nLocale} from './type'; @@ -270,7 +270,7 @@ export function getLocaleFromRequest(request: Request): I18nLocale { } export function usePrefixPathWithLocale(path: string) { - const rootData = useRootLoaderData(); + const rootData = useRouteLoaderData('root'); const selectedLocale = rootData?.selectedLocale ?? DEFAULT_LOCALE; return `${selectedLocale.pathPrefix}${ @@ -280,7 +280,7 @@ export function usePrefixPathWithLocale(path: string) { export function useIsHomePath() { const {pathname} = useLocation(); - const rootData = useRootLoaderData(); + const rootData = useRouteLoaderData('root'); const selectedLocale = rootData?.selectedLocale ?? DEFAULT_LOCALE; const strippedPathname = pathname.replace(selectedLocale.pathPrefix, ''); return strippedPathname === '/'; diff --git a/app/root.tsx b/app/root.tsx index b1bbba6..4c1e303 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -12,7 +12,7 @@ import { Outlet, Scripts, ScrollRestoration, - useLoaderData, + useRouteLoaderData, useRouteError, type ShouldRevalidateFunction, } from '@remix-run/react'; @@ -25,15 +25,17 @@ import { } from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; -import {Layout} from '~/components/Layout'; +import {PageLayout} from '~/components/PageLayout'; +import {GenericError} from '~/components/GenericError'; +import {NotFound} from '~/components/NotFound'; +import favicon from '~/assets/favicon.svg'; import {seoPayload} from '~/lib/seo.server'; +import styles from '~/styles/app.css?url'; -import favicon from './assets/favicon.svg'; -import {GenericError} from './components/GenericError'; -import {NotFound} from './components/NotFound'; -import styles from './styles/app.css?url'; import {DEFAULT_LOCALE, parseMenu} from './lib/utils'; +export type RootLoader = typeof loader; + // This is important to avoid re-fetching root queries on sub-navigations export const shouldRevalidate: ShouldRevalidateFunction = ({ formMethod, @@ -103,10 +105,10 @@ export const meta = ({data}: MetaArgs) => { return getSeoMeta(data!.seo as SeoConfig); }; -export default function App() { +function Layout({children}: {children?: React.ReactNode}) { const nonce = useNonce(); - const data = useLoaderData(); - const locale = data.selectedLocale ?? DEFAULT_LOCALE; + const data = useRouteLoaderData('root'); + const locale = data?.selectedLocale ?? DEFAULT_LOCALE; return ( @@ -118,18 +120,22 @@ export default function App() { - - - - - + + {children} + + + ) : ( + children + )} @@ -137,11 +143,16 @@ export default function App() { ); } +export default function App() { + return ( + + + + ); +} + export function ErrorBoundary({error}: {error: Error}) { - const nonce = useNonce(); const routeError = useRouteError(); - const rootData = useLoaderData(); - const locale = rootData?.selectedLocale ?? DEFAULT_LOCALE; const isRouteError = isRouteErrorResponse(routeError); let title = 'Error'; @@ -153,37 +164,21 @@ export function ErrorBoundary({error}: {error: Error}) { } return ( - - - - - {title} - - - - - - {isRouteError ? ( - <> - {routeError.status === 404 ? ( - - ) : ( - - )} - + + {isRouteError ? ( + <> + {routeError.status === 404 ? ( + ) : ( - + )} - - - - - + + ) : ( + + )} + ); } diff --git a/app/routes/($locale).cart.tsx b/app/routes/($locale).cart.tsx index e694926..aff1d6b 100644 --- a/app/routes/($locale).cart.tsx +++ b/app/routes/($locale).cart.tsx @@ -1,4 +1,4 @@ -import {Await} from '@remix-run/react'; +import {Await, useRouteLoaderData} from '@remix-run/react'; import invariant from 'tiny-invariant'; import { type LoaderFunctionArgs, @@ -13,7 +13,7 @@ import { import {isLocalPath} from '~/lib/utils'; import {Cart} from '~/components/Cart'; -import {useRootLoaderData} from '~/hooks/useRootLoaderData'; +import type {RootLoader} from '~/root'; export async function action({request, context}: ActionFunctionArgs) { const {cart} = context; @@ -90,7 +90,9 @@ export async function loader({context}: LoaderFunctionArgs) { } export default function CartRoute() { - const rootData = useRootLoaderData(); + const rootData = useRouteLoaderData('root'); + if (!rootData) return null; + // @todo: finish on a separate PR return ( <>