codegino
← all posts

Getting started with Remix

How to start working on a Remix project

7 min read#react#tech#remix

DJ mixer
DJ mixer

What is Remix?

Remix is a "relatively" new framework which was open sourced on 23rd of November 2021. It was originally created by the awesome Ryan Florence and Michael Jackson, and with the recent addition of Kent C. Dodds, it allows the framework to sell itself.

As per their website

Remix is a full stack web framework that let’s you focus on the user interface and work back through web fundamentals to deliver a fast, slick, and resilient user experience. People are gonna love using your stuff.

Let's get started with the actual coding.


Create a basic Remix app

Prerequisites

  • Make sure you have node installed
  • Basic understanding of React
  • Basic cli knowledge

Installation

sh
npx create-remix@latest# follow the promptscd [whatever you named the project]

NOTE: There will be an option to run npm install to install the dependencies immediately. This will create a package-lock.json. If you want to use yarn, you can skip this step, but don't forget to run yarn install later.

Running the app

Based on what you choose in the image below, a custom README.md file is created at the project's root. Make sure to check the steps on how to run the application locally

An image of a blog post

Running examples

You can use yarn for the steps below if you prefer

For Remix App Server

sh
1npm run dev

For Express Server

sh
1# Start the Remix development asset server2$ npm run dev3
4# In a new tab start your express app:5npm run start:dev

You should see something like this:

An image of a blog post

If you don't, make sure to check README.md for specific instructions on how to run the app locally,


I will be using TypeScript for this blog; if you prefer to use vanilla JavaScript, remove the type usages and change the extensions from .tsx to .jsx.

Cleaning up

Let's start coding with a clean slate.

sh
1# Remove demo files2rm -rf app/routes/demos app/styles/demos3
4# We'll recreate this files later5rm app/routes/index.tsx app/root.tsx

Create a file named root.tsx file under app folder.

Let's proceed with the mandatory hello world example.

app/root.tsx
1import { LiveReload } from "remix";2
3export default function App() {4  return (5    <html lang="en">6      <head>7        <meta charSet="utf-8" />8      </head>9      <body>10        <h2>Hello World</h2>11        {process.env.NODE_ENV === "development" ? (12          <LiveReload />13        ) : null}14      </body>15    </html>16  );17}

Hello World, indeed.

An image of a blog post

Since this file will serve as the global container for the app, let's refactor it to make this more generic.

app/root.tsx
1import {Links,LiveReload,Meta,Outlet,Scripts,ScrollRestoration} from "remix";2
3export default function App() {4  return (5    <Document>6      <Layout>7        <Outlet />8      </Layout>9    </Document>10  );11}12
13// Here is the blueprint of our document14// It looks like our typical HTML but with a few extra tags15// I will discuss in another blog post those Components coming from the remix package16function Document({17  children,18  title,19}: {20  children: React.ReactNode;21  title?: string;22}) {23  return (24    <html lang="en">25      <head>26        <meta charSet="utf-8" />27        <meta name="viewport" content="width=device-width,initial-scale=1" />28        {title ? <title>{title}</title> : null}29        <Meta />30        <Links />31      </head>32      <body>33        {children}34        <ScrollRestoration />35        <Scripts />36        {process.env.NODE_ENV === "development" && <LiveReload />}37      </body>38    </html>39  );40}41
42// Layout is a wrapper component that provides a consistent layout for all pages.43function Layout({ children }: React.PropsWithChildren<{}>) {44  return <main>{children}</main>;45}

Create the index route file index.tsx under app/routes folder.

Now, We extract the content of our page

app/routes/index.tsx
1export default function Index() {2  return <div>3    <h2>Hello World</h2>4  </div>5}

The two changes above will still yield the same result:

An image of a blog post

We'll add the links inside Layout since it will be reusable across all pages.

app/root.tsx
1import {Link /*other import*/} from "remix";2// ...3function Layout({ children }: React.PropsWithChildren<{}>) {4  return (5    <main>6      <header>7        <ul>8          <li>9            <Link to="/pokemons">Pokemons</Link>10          </li>11        </ul>12      </header>13
14      {children}15    </main>16  );17}18// ...

Result:

An image of a blog post

After clicking the link or navigating to the URL, you should see something like this:

An image of a blog post

It is expected since we have not created a route handler for the /pokemons page.


Before creating that route, let us use CatchBoundary and useCatch to create a custom 404 error message as a fallback for all Not Found routes.

app/root.tsx
1import { useCatch /*other imports*/ } from "remix";2// ...3export function CatchBoundary() {4  let caught = useCatch();5
6  let message;7  switch (caught.status) {8    case 404:9      message = <p>This is a custom error message for 404 pages</p>10      break;11    // You can customize the behavior for other status codes12    default:13      throw new Error(caught.data || caught.statusText);14  }15
16  return (17    <Document title={`${caught.status} ${caught.statusText}`}>18      <Layout>19        <h1>20          {caught.status}: {caught.statusText}21        </h1>22        {message}23      </Layout>24    </Document>25  );26}27// ...

Here is the customized 404 error page:

An image of a blog post

This is one of Remix's magics; by simply following a convention, we can simplify common use cases. In the above case, we exported a function named CatchBoundary where we used useCatch inside to get a context about the error. Remix will do the heavy lifting; we simply need to adhere to, let's call it, a "contract function".


To fix this 404 error, let's create the /pokemons route

app/routes/pokemons/index.tsx
1export default function Pokemons() {2  return (3    <div>4      <h2>Pokemons</h2>5    </div>6  );7}
An image of a blog post

Adding meta tags

Meta tags are used here to update the title and description of the page. To learn more what meta is used for, check this


app/routes/pokemons.tsx
1// This is another "contract function"2export function meta() {3  return {4    title: 'Pokemons',5    description: 'List of Pokemons',6  }7}8
9// export default function Pokemons...

We should see an updated head

An image of a blog post

Fetching Data

Unlike the vanilla React where usually fetch the data from the client-side, in Remix we can load data from the server using a the concept of a loader


Create a Loader

app/routes/pokemons.tsx
1// previous imports2import type { LoaderFunction } from "remix"3
4// This is another "contract function"5export const loader: LoaderFunction = () => {6  return fetch("https://pokeapi.co/api/v2/pokemon")7}8
9// export default function Pokemons...

If you are wondering where is the .then(res => res.json()) part, you are not alone.

I'm still cheking how they allow this magic to happen.

NOTE: At the time of this writing, I don't know why there will be an error going back and forth on the pokemons listing page. As per reason, I will still append the .then(res => res.json()) to the loader function.


Accessing data in React

Use the useLoaderData hook to access the data in React land.

app/routes/pokemons.tsx
1// ...2import { useLoaderData, Link /*other imports*/ } from 'remix'3
4// export let loader: LoaderFunction...5
6export default function Pokemons() {7  const data = useLoaderData()8
9  // Try to use console.log here10
11  return (12    <div>13      <h2>Pokemons</h2>14      <ul>15        {data.results.map(pokemon => (16          <li key={pokemon.name}>17            <Link to={`/pokemons/${pokemon.name}`}>{pokemon.name}</Link>18          </li>19        ))}20      </ul>21    </div>22  )23}

Combining the two previous codes will result to:

An image of a blog post

Creating a dynamic route

For this demo, let's use the file path convention.

Under the pokemons folder, create a folder named $pokemonName.tsx. Yes, it's not a typo; add a $ before the file name. We'll see how to use it later.

app/routes/pokemons/$pokemonName.tsx
1export default function Pokemon() {2  return (3    <div>4      <h1>Specific Pokemon Route</h1>5    </div>6  );7}

If we click bulbasaur in the list, we should see something like this:

An image of a blog post

Now, how do we customize the page to show the details of a Pokemon?


By naming the file $pokemonName.tsx, inside the file, we can access pokemonName inside the params object.

We can use this information to fetch the specific data from the server. see line #9

app/routes/pokemons/$pokemonName.tsx
1import { useLoaderData } from "remix"2import type { LoaderFunction } from "remix"3
4export let loader: LoaderFunction = async ({ params }) => {5  const pokemonName = params.pokemonName;6  // OR const { pokemonName } = params;7
8  const details = await fetch(9    `https://pokeapi.co/api/v2/pokemon/${pokemonName}`10  ).then((res) => res.json());11
12  // We'll map the data based on our needs13  return {14    name: pokemonName,15    weight: details.weight,16    img: details.sprites.front_default,17    id: details.id,18  };19};20
21export default function Pokemon() {22  const pokemon = useLoaderData();23
24  return (25    <div>26      <h1>27        {pokemon.name} #{pokemon.id}28      </h1>29      <img src={pokemon.img} alt={pokemon.name} />30      <p>Weight: {pokemon.weight}</p>31    </div>32  );33}

With the code above, we can show these details in our page

An image of a blog post

Update meta of pokemon route

Before wrapping this up, let's update the meta of the Pokemon details page.

app/routes/pokemons/$pokemonName.tsx
1import type { MetaFunction } from "remix"2
3// You can access the `loader` data here4export const meta: MetaFunction = ({ data }) => {5  return {6    title: `#${data.id} ${data.name}`,7    description: `Details of ${data.name}`,8  };9}

And here is a page with a better title and description

An image of a blog post

Putting all the codes together

Link to the source Here's a demo

Conclusion

This is still a pretty small application for me to assess what Remix can do, and I have not even gotten into one of their selling points, the Nested Routes. But, so far, I like how easy it is to create an application from scratch, and I find the convention easy to follow. I also like how they provide hooks to more conveniently work with the data, errors, etc. Having said that, I definitely will explore more about Remix and the ecosystem in the future.