Using the Remix loader on server and client

Allrighty, now that I got the Cloudflare D1 database setup, it’s time to dig into one of the features of Remix that drew me in — the loader.

Updating the loader function

The Niuite page started as a simple listing of all the available shirts with a default sort of created date descending. Now I’m adding a select component to the page so you can select the order you would like to see the shirts.

This requires two updates to the loader function:

  1. Check the request for a “sort_by” parameter
  2. Create an object to be used as the sort order for the workers-qb request
  3. Pass the sortBy value to be used when rendering the page
export const loader = async ({ context, request }: LoaderFunctionArgs) => {
  let sortBy = new URL(request.url).searchParams.get("sort_by");
  let sortOrder: string | Record<string, string> | string[] | undefined = {
    createdDate: OrderTypes.DESC,
  };
  if (sortBy) {
    switch (sortBy) {
      case "name-ascending":
        sortOrder = { name: OrderTypes.ASC };
        break;
      case "name-descending":
        sortOrder = { name: OrderTypes.DESC };
        break;
      case "date-ascending":
        sortOrder = { createdDate: OrderTypes.ASC };
        break;
    }
  } else {
    sortBy = "date-descending";
  }

  const { env } = context.cloudflare;
  const qb = new D1QB(env.DB);

  const query = await qb
    .fetchAll<Shirt>({
      tableName: "shirts",
      orderBy: sortOrder,
    })
    .execute();

  const shirts = query.results;

  return json({ shirts, sortBy });
};

Updating the page function

Now I can initialize the dropdown value and hook up the data call to the select component changing.

The code changes required include:

  1. Adding a Form and select component
  2. Using the sortBy value as the defaultValue of the select
  3. Hooking up useSubmit so the Form action fires when the selected value changes.
import { Form, useLoaderData, useSubmit } from "@remix-run/react";

export default function ShirtsIndex() {
    const { shirts, sortBy } = useLoaderData<typeof loader>();
    const submit = useSubmit();

    ...

    <div>
        Sort by:
        <Form onChange={(event) => {
            submit(event.currentTarget);
        }}>
            <select name="sort_by" defaultValue={sortBy} className='p-1'>
                <option value="name-ascending">Alphabetically, A-Z</option>
                <option value="name-descending">Alphabetically, Z-A</option>
                <option value="date-ascending">Date, old to new</option>
                <option value="date-descending">Date, new to old</option>
            </select>
        </Form>
    </div>

Did you see it?

That’s one function for both server and client side data. The initial render loads with the default sort order or when set by the sort_by request parameter and brings down the whole page.

Changing the value of the sort select fetches the data client side and updates the UI.

Beautiful.