umma.dev

Remix

Another day, another front-end framework!

What makes Remix different?

Remix takes advantage of the server client model; with many things done on the server side it makes applications extremely fast.

Combines HTTP caching, URL assets and dynamic server rendering.

Structure

When you spin up a simple remix app, you’ll see two folders, app and public.

Within app there is a route folder. This acts like an index file on the application.

You can add folders such as styles within the app folder, as well as others such as config.

The public folder, like other web applications, is used to store static files such as fav icons.

Routing/Pages

When creating routes, it’s as simple as creating a folder under routes and then adding an index file.

For example:

app/routes/new-page/index.tsx

export default function NewPage() {
  return (
    <main>
      <p>My new page</p>
    </main>
  )
}

Data

In Remix, you can create an API route, this provides data and a front-end component that consumes it.

Remix includes something called loaders, these only run on the server and are the backend “API” for the component. It’s used within components via useLoaderData.

import { useLoaderData } from 'remix'

export const loader = async () => {
 return ['one', 'two']
}

export default function Example() {
  const data = useLoaderData()
  return(
    <div>
    <h1>Some data</h1>
    <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

Here’s a complete example, taken from the docs:

// app/routes/posts/index.tsx

import { json } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";

type Post = {
  slug: string;
  title: string;
};

type LoaderData = {
  posts: Array<Post>;
};

export const loader = async () => {
  return json<LoaderData>({
    posts: [
      {
        slug: "my-first-post",
        title: "My First Post",
      },
      {
        slug: "90s-mixtape",
        title: "A Mixtape I Made Just For You",
      },
    ],
  });
};

export default function Posts() {
  const { posts } = useLoaderData() as LoaderData;
  return (
    <main>
      <h1>Posts</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.slug}>
            <Link
              to={post.slug}
              className="text-blue-600 underline"
            >
              {post.title}
            </Link>
          </li>
        ))}
      </ul>
    </main>
  );
}

The above example can be refactored. It is better to create modules that deal with reading and writing posts. To do this to the example above, you can set up a getPosts export.

// app/models/post.server.ts

type Post = {
  slug: string;
  title: string;
};

export async function getPosts(): Promise<Array<Post>> {
  return [
    {
      slug: "my-first-post",
      title: "My First Post",
    },
    {
      slug: "90s-mixtape",
      title: "A Mixtape I Made Just For You",
    },
  ];
}

The post route can be updated as follows:

import { json } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";

import { getPosts } from "~/models/post.server";

type LoaderData = {
  // this is a handy way to say: "posts is whatever type getPosts resolves to"
  posts: Awaited<ReturnType<typeof getPosts>>;
};

export const loader = async () => {
  return json<LoaderData>({
    posts: await getPosts(),
  });
};

Example