Skip to content

Server/client communication with useLoaderData

In the previous step, we learned about route parameters and how to use them to create dynamic routes in Remix. We then accessed this data on the server using the loader function.

In this next step we will:

  • Pass the category value to our NewsCategoryPage component and display it on the page with the useLoaderData hook.
  • Fix a TypeScript error by using the invariant package to tell TypeScript that the categoryTitle value should always be a string.
  • Complete a challenge to create a wireframe layout for the NewsCategoryPage using Tailwind CSS.

Success! With our previous step complete, we are now reading the category value from the URL ๐ŸŽ‰. We can use this value to fetch the correct data for our page.

Letโ€™s update the loader function so it returns this value in title-case as part of the json response.

app/routes/news.$category.tsx
import { type LoaderFunctionArgs, json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { toTitleCase } from '~/utils/stringUtils.ts'
export async function loader({ params }: LoaderFunctionArgs) {
const { category } = params
console.log({ category })
const categoryTitle = toTitleCase(category)
return json({})
return json({ categoryTitle })
}
export default function NewsCategoryPage() {
const { categoryTitle } = useLoaderData<typeof loader>()
return (
<div className="container py-16">
<h2 className="text-h2">Generic news category page</h2>
<h2 className="text-h2">{categoryTitle}</h2>
</div>
)
}

Now, head back to your browser and click the links between the different news categories again.

This time, you should see the title of the category displayed in every page, but we didnโ€™t need to hard-code a separate file for every one ๐ŸŽ‰:

Custom title on every dynamic route

Finally, letโ€™s fix that TypeScript error we saw earlier.

We will do this using a package called Invariant. This package will help us to tell TypeScript that the categoryTitle value will always be a string.

First, import the package at the top of your news.$category.tsx file:

app/routes/news.$category.tsx
import { invariant } from '@epic-web/invariant'
import { type LoaderFunctionArgs, json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { toTitleCase } from '~/utils/stringUtils.ts'

Next, inside your loader function, add the following check:

app/routes/news.$category.tsx
4 collapsed lines
import { invariant } from '@epic-web/invariant'
import { type LoaderFunctionArgs, json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { toTitleCase } from '~/utils/stringUtils.ts'
export async function loader({ params }: LoaderFunctionArgs) {
const { category } = params
invariant(typeof category === 'string', 'Category not found')
const categoryTitle = toTitleCase(category)
return json({ categoryTitle })
}
9 collapsed lines
export default function NewsCategoryPage() {
const { categoryTitle } = useLoaderData<typeof loader>()
return (
<div className="container py-16">
<h2 className="text-h2">{categoryTitle}</h2>
</div>
)
}

The red line has disappeared, and TypeScript now knows that categoryTitle will always be a string.

In this step, we:

  • Read server data from the loader function using the useLoaderData hook, and displayed it on the page.
  • Fixed a TypeScript error by using the invariant package to tell TypeScript that the categoryTitle value will always be a string.