Skip to content

The `useLoaderData` hook

In a typical website, code can be split into two main parts:

  1. Frontend code

    • runs in the userโ€™s browser (the โ€˜clientโ€™)
    • written in HTML, CSS and JavaScript
    • responsible for
      • rendering the website
      • handling user interactions
      • sending HTTP requests to the server
  2. Backend code:

    • runs on the server.
    • written in JavaScript
    • responsible for
      • database fetching and updates
      • authentication and authorisation
      • providing HTTP responses to the client

Front and backend code

In Remix, front and backend code is combined into a single file called a route module.

The file we have been working in - root.tsx - is an example of this.

Take another look at the app/root.tsx file:

app/root.tsx
import { useLoaderData } from 'react-router'
import { type Route } from './+types/root.ts'
import { type loader } from './__root.server.tsx'
import { GeneralErrorBoundary } from './components/error-boundary.tsx'
import Document from './components/shared-layout/Document.tsx'
import { useNonce } from './utils/nonce-provider.ts'
import rootLinkElements from './utils/providers/rootLinkElements.ts'
export const links: Route.LinksFunction = () => {
return rootLinkElements
}
export { meta } from './__root.client.tsx'
export { headers, loader } from './__root.server.tsx'
export default function App() {
const data = useLoaderData<typeof loader | null>()
const nonce = useNonce()
return (
<Document nonce={nonce} honeyProps={data?.honeyProps}>
<div className="flex h-screen flex-col justify-between">
<div className="flex-1">
<main className="grid h-full place-items-center">
<h1 className="text-mega">Welcome to Epic News!</h1>
</main>
</div>
</div>
</Document>
)
}
export const ErrorBoundary = GeneralErrorBoundary

Youโ€™ll notice that we are exporting a function from this file called: loader.

export { headers, loader } from './__root.server.tsx'

This function is responsible for fetching data from the server and processing it before it is sent to the frontend.

But where is this function defined? The line highlighted above says that the loader function is being exported somehow, but we donโ€™t see them in this file. ๐Ÿง

Thatโ€™s because it is defined in a completely separate file: __root.server.tsx.

If you are interested, open up the __root.server.tsx file and take a look at the loader function. Youโ€™ll see that it is responsible for fetching data from the server and sending it to the frontend.

We will be looking more closely at the loader function in a later tutorial.

useLoaderData is a custom React hook supplied for us by Remix.

Here, it is being used to pass the data from the loader function in __root.server.tsx into the App component running in the browser.

We will look more at this hook later in the module, but for now lets use another hook to get our theme switch working.

Making sure you are still inside app/root.tsx, letโ€™s start by updating the import statements at the top of the file.

Add the code highlighted in green to the end of your import statements in app/root.tsx:

app/root.tsx
import { type LinksFunction } from '@remix-run/node'
import Document from '~/components/shared-layout/Document'
import { useNonce } from '~/utils/nonce-provider.ts'
import rootLinkElements from '~/utils/providers/rootLinkElements'
import { ThemeSwitch, useTheme } from './routes/resources+/theme-switch.tsx'

Next, carefully add the code highlighted in green below to the App component function itself:

app/root.tsx
14 collapsed lines
import { useLoaderData } from 'react-router'
import { type Route } from './+types/root.ts'
import { type loader } from './__root.server.tsx'
import { GeneralErrorBoundary } from './components/error-boundary.tsx'
import Document from './components/shared-layout/Document.tsx'
import { useNonce } from './utils/nonce-provider.ts'
import rootLinkElements from './utils/providers/rootLinkElements.ts'
import { ThemeSwitch, useTheme } from './routes/resources+/theme-switch.tsx'
export const links: LinksFunction = () => {
return rootLinkElements
}
export { meta } from './__root.client.tsx'
export { headers, loader } from './__root.server.tsx'
export default function App() {
const data = useLoaderData<typeof loader | null>();
const nonce = useNonce();
const theme = useTheme()
return (
<Document theme={theme} nonce={nonce} honeyProps={data?.honeyProps}>
<div className="flex h-screen flex-col justify-between">
<div className="flex-1">
<main className="grid h-full place-items-center">
<h1 className="text-mega">Welcome to Epic News!</h1>
</main>
</div>
<div className="container flex justify-between pb-5">
<ThemeSwitch userPreference={data?.requestInfo.userPrefs.theme} />
</div>
</div>
</Document>
)
}

When you are ready, open the explanation below.

useTheme is a custom React hook (i.e. a function that can only be used inside a React component).

Itโ€™s a function that:

  1. Figures out what theme the user wants (light mode or dark mode)
  2. Returns that theme as a string (either 'light' or 'dark')
  3. Stores the result in the theme variable
app/root.tsx
const theme = useTheme()

Data Type: The theme variable is a string.

Possible Values:

  • 'light' - Light mode (light background, dark text)
  • 'dark' - Dark mode (dark background, light text)

Example:

// If the user prefers dark mode:
const theme = useTheme() // theme = 'dark'
// If the user prefers light mode:
const theme = useTheme() // theme = 'light'

The theme variable is passed to the Document component:

<Document theme={theme} nonce={nonce} honeyProps={data?.honeyProps}>
{/* ... rest of the app ... */}
</Document>
  • Weโ€™re passing the theme value as a prop (property) to the Document component
  • The Document component will use this value to apply the correct styling to the entire page

A few lines down, we pass this into the ThemeSwitch component:

app/root.tsx
<ThemeSwitch userPreference={data?.requestInfo.userPrefs.theme} />

The ThemeSwitch component is a button that lets users change the theme of the website.

User Experience:

  1. User sees an icon (sun, moon, or laptop)
  2. User clicks the icon
  3. The theme changes (light โ†’ dark โ†’ system โ†’ light โ†’ โ€ฆ)
  4. The icon updates to show the current mode

The Three Modes:

  • Light mode (โ˜€๏ธ sun icon): Everything is light-colored
  • Dark mode (๐ŸŒ™ moon icon): Everything is dark-colored
  • System mode (๐Ÿ’ป laptop icon): Matches your computer/phoneโ€™s theme setting
1. User clicks the button
โ†“
2. Form submits with nextMode value (e.g., 'dark')
โ†“
3. Request sent to /resources/theme-switch
โ†“
4. Server action runs (saves theme in cookie)
โ†“
5. useOptimisticThemeMode detects the change immediately
โ†“
6. useTheme returns the new theme
โ†“
7. Document component gets new theme
โ†“
8. Page updates with new colors!
Explanation ๐Ÿค“

Compare your notes with the explanation above. Where there are differences, make a note of these.

Save your changes, then head back to the browser. You should now be able to toggle the theme between light and dark mode by clicking the button:

Light and dark mode toggle button

You now have a working light and dark mode toggle button in your Remix app! ๐Ÿš€

Finally, lets fix the yellow underlines that might still be showing under your new import statements.

As mentioned earlier, VS Code is warning us that we havenโ€™t used the values, variables and functions that we are importing yet.

Thankfully, VS Code provides us with a keyboard shortcut to automatically organise our imports in the correct order.

  1. Windows: SHIFT + ALT + O
  2. Mac: SHIFT + OPTION + O

Press this shortcut now to automatically organise your imports at the top of your file:

Auto sort imports in root.tsx

Remember to save your file after you have done this, and commit your changes to Git.

Support your explanations with screenshots and code snippets where necessary.

In the next step, we will add a navbar and footer to the website to introduce navigation and improve user experience.