Skip to content

Commit

Permalink
support layout component
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Oct 21, 2024
1 parent 7cd7e17 commit a537be6
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 36 deletions.
53 changes: 23 additions & 30 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ import {
} from './components/ui/dropdown-menu.tsx'
import { Icon, href as iconsHref } from './components/ui/icon.tsx'
import { EpicToaster } from './components/ui/sonner.tsx'
import { ThemeSwitch, useTheme } from './routes/resources+/theme-switch.tsx'
import {
ThemeSwitch,
useOptionalTheme,
useTheme,
} from './routes/resources+/theme-switch.tsx'
import tailwindStyleSheetUrl from './styles/tailwind.css?url'
import { getUserId, logout } from './utils/auth.server.ts'
import { ClientHintCheck, getHints } from './utils/client-hints.tsx'
Expand Down Expand Up @@ -153,14 +157,13 @@ function Document({
nonce,
theme = 'light',
env = {},
allowIndexing = true,
}: {
children: React.ReactNode
nonce: string
theme?: Theme
env?: Record<string, string>
allowIndexing?: boolean
}) {
const allowIndexing = ENV.ALLOW_INDEXING !== 'false'
return (
<html lang="en" className={`${theme} h-full overflow-x-hidden`}>
<head>
Expand Down Expand Up @@ -188,24 +191,29 @@ function Document({
)
}

export function Layout({ children }: { children: React.ReactNode }) {
// if there was an error running the loader, data could be missing
const data = useLoaderData<typeof loader | null>()
const nonce = useNonce()
const theme = useOptionalTheme()
return (
<Document nonce={nonce} theme={theme} env={data?.ENV}>
{children}
</Document>
)
}

function App() {
const data = useLoaderData<typeof loader>()
const nonce = useNonce()
const user = useOptionalUser()
const theme = useTheme()
const matches = useMatches()
const isOnSearchPage = matches.find((m) => m.id === 'routes/users+/index')
const searchBar = isOnSearchPage ? null : <SearchBar status="idle" />
const allowIndexing = data.ENV.ALLOW_INDEXING !== 'false'
useToast(data.toast)

return (
<Document
nonce={nonce}
theme={theme}
allowIndexing={allowIndexing}
env={data.ENV}
>
<>
<div className="flex h-screen flex-col justify-between">
<header className="container py-6">
<nav className="flex flex-wrap items-center justify-between gap-4 sm:flex-nowrap md:gap-8">
Expand Down Expand Up @@ -237,7 +245,7 @@ function App() {
</div>
<EpicToaster closeButton position="top-center" theme={theme} />
<EpicProgress />
</Document>
</>
)
}

Expand Down Expand Up @@ -326,21 +334,6 @@ function UserDropdown() {
)
}

export function ErrorBoundary() {
// the nonce doesn't rely on the loader so we can access that
const nonce = useNonce()

// NOTE: you cannot use useLoaderData in an ErrorBoundary because the loader
// likely failed to run so we have to do the best we can.
// We could probably do better than this (it's possible the loader did run).
// This would require a change in Remix.

// Just make sure your root route never errors out and you'll always be able
// to give the user a better UX.

return (
<Document nonce={nonce}>
<GeneralErrorBoundary />
</Document>
)
}
// this is a last resort error boundary. There's not much useful information we
// can offer at this level.
export const ErrorBoundary = GeneralErrorBoundary
17 changes: 15 additions & 2 deletions app/routes/resources+/theme-switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import { redirect, useFetcher, useFetchers } from '@remix-run/react'
import { ServerOnly } from 'remix-utils/server-only'
import { z } from 'zod'
import { Icon } from '#app/components/ui/icon.tsx'
import { useHints } from '#app/utils/client-hints.tsx'
import { useRequestInfo } from '#app/utils/request-info.ts'
import { useHints, useOptionalHints } from '#app/utils/client-hints.tsx'
import {
useOptionalRequestInfo,
useRequestInfo,
} from '#app/utils/request-info.ts'
import { type Theme, setTheme } from '#app/utils/theme.server.ts'

const ThemeFormSchema = z.object({
Expand Down Expand Up @@ -129,3 +132,13 @@ export function useTheme() {
}
return requestInfo.userPrefs.theme ?? hints.theme
}

export function useOptionalTheme() {
const optionalHints = useOptionalHints()
const optionalRequestInfo = useOptionalRequestInfo()
const optimisticMode = useOptimisticThemeMode()
if (optimisticMode) {
return optimisticMode === 'system' ? optionalHints?.theme : optimisticMode
}
return optionalRequestInfo?.userPrefs.theme ?? optionalHints?.theme
}
7 changes: 6 additions & 1 deletion app/utils/client-hints.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { clientHint as timeZoneHint } from '@epic-web/client-hints/time-zone'
import { useRevalidator } from '@remix-run/react'
import * as React from 'react'
import { useRequestInfo } from './request-info.ts'
import { useOptionalRequestInfo, useRequestInfo } from './request-info.ts'

const hintsUtils = getHintUtils({
theme: colorSchemeHint,
Expand All @@ -28,6 +28,11 @@ export function useHints() {
return requestInfo.hints
}

export function useOptionalHints() {
const requestInfo = useOptionalRequestInfo()
return requestInfo?.hints
}

/**
* @returns inline script element that checks for client hints and sets cookies
* if they are not set then reloads the page if any cookie was set to an
Expand Down
12 changes: 9 additions & 3 deletions app/utils/request-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ import { useRouteLoaderData } from '@remix-run/react'
import { type loader as rootLoader } from '#app/root.tsx'

/**
* @returns the request info from the root loader
* @returns the request info from the root loader (throws an error if it does not exist)
*/
export function useRequestInfo() {
const maybeRequestInfo = useOptionalRequestInfo()
invariant(maybeRequestInfo, 'No requestInfo found in root loader')

return maybeRequestInfo
}

export function useOptionalRequestInfo() {
const data = useRouteLoaderData<typeof rootLoader>('root')
invariant(data?.requestInfo, 'No requestInfo found in root loader')

return data.requestInfo
return data?.requestInfo
}

0 comments on commit a537be6

Please sign in to comment.