Is Remix your next framework?

Michael Jackson

Ryan Florence

Kent C Dodds

Remix is driven by community leaders

Reaching the goal

Perhaps JavaScript should not be the starting point for the developer, but should be thought of as an extension.

Works without JavaScript

CSR vs SSR vs SSG vs ISR

Where to render

Client Side Rendering (CSR)

Server Side Rendering (SSR)

Server Side Generation (SSG/ISR)

Remix

Remix

Using what we know

It's just React

Without
the hard parts

React without
the hard parts

 Learn Remix, Accidentally Learn the Web

Full Stack

Remix vs NextJS

Remix is all about

  • Developer experience (DX)
  • Performance & speed (UX)
  • Using web standard

Routing

app
├── root.jsx
└── routes
    ├── accounts.jsx
    ├── dashboard.jsx
    ├── expenses.jsx
    ├── index.jsx
    ├── reports.jsx
    ├── sales
    │   ├── customers.jsx
    │   ├── deposits.jsx
    │   ├── index.jsx
    │   ├── invoices
    │   │   ├── $invoiceId.jsx
    │   │   └── index.jsx
    │   ├── invoices.jsx
    │   └── subscriptions.jsx
    └── sales.jsx

Defining routes

/sales/invoices/102000

- root.jsx
- routes/sales.jsx
- routes/sales/invoices.jsx
- routes/sales/invoices/$invoiceId.jsx
<Root>
  <Sales>
    <Invoices>
      <InvoiceId />
    </Invoices>
  </Sales>
</Root>

Data Loading

Time to First Byte (TTFB)

First Contentful Paint (FCP)

Cumulative Layout Shift (CLS)

Largest Contentful Paint (LCP)

Time To Interactive (TTI)

How can we describe performance

Rendering of a standard SPA

Parallel data fetching

Server side rendering

Server side rendering with caching

import type { LoaderFunction } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useLoaderData } from "@remix-run/react";
import { db } from "~/db.server";

export const loader: LoaderFunction = async ({ params }) => {
  return json(
    await db.product.findMany({
      where: { categoryId: params.categoryId },
    })
  );
};

export default function ProductCategory() {
  const products = useLoaderData();
  return (
    <div>
      <p>{products.length} Products</p>
      {/* ... */}
    </div>
  );
}

Route with data fetching

FORM ACTIONs

function MyForm() {
  const [inputs, setInputs] = useState({});

  const handleChange = (event) => {
    const name = event.target.name;
    const value = event.target.value;
    setInputs(values => ({...values, [name]: value}))
  }

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log(inputs);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>Enter your name:
        <input type="text" 
               value={inputs.username || ""}
               onChange={handleChange}
        />
      </label>
    </form>
  )
}

React Form

The power of Remix

GET /users

GET /users?name=peter

import type { LoaderArgs } from "@remix-run/node"; // or cloudflare/deno
import { redirect } from "@remix-run/node"; // or cloudflare/deno

export const loader = async ({ request }: LoaderArgs) => {
  const url = new URL(request.url);
  const search = new URLSearchParams(url.search);
  const query = search.get('query') || '';
  const data = [....];
  return json({ query, data });
};

export default function Users() {
  const { data, query } = useLoaderData();
  return (
    <h1>Search page</h1>
    <form method='get'>
      <input type='text' name='query' defaultValue={query}/>
    </form>
  )
}

Filter form (get)

import type { ActionArgs } from "@remix-run/node"; // or cloudflare/deno
import { redirect } from "@remix-run/node"; // or cloudflare/deno

export const action = async ({ request }: ActionArgs) => {
  const formData = await request.formData();
  const user = await db.save.user(formData);
  return redirect(`/users/${user.id}`);
};

export default function NewUser() {
  return (
    <form method="post" action="/search">
      <input name="name" type="text" />
      <input name="email" type="email" />
      <button type="submit">Submit</button>
    </form>
  )
}

Mutation form (post)

// Zod validation schema
export const formSchema = z.object({
  name: z.string(),
  email: z.string().email().optional()
});

export const action = async ({ request }) => {
  const formPayload = Object.fromEntries(await request.formData());
  try {
    const formValues = formSchema.parse(formPayload);
    const user = await db.save.user(formValues);
    return redirect(`/users/${user.id}`);
  } catch (error) {
    return {
      formPayload,
      errors: (error as ZodError).flatten().fieldErrors,
    };
  }
};

Form validation - action

export default function NewUser() {
  const actionData = useActionData();
  return (
    <form method="get" action="/search">
      <input name="name" type="text" />
      {actionData?.errors.name && (
         <span className="text-red-600">{actionData?.errors.name[0]}</span>
      )}
      <input name=email" type="email" />
      {actionData?.email.name && (
         <span className="text-red-600">{actionData?.errors.email[0]}</span>
      )}
      <button type="submit">Submit</button>
    </form>
  )
}

Form validation - handle errors

ERROR Handling

// root.aspx
export function ErrorBoundary({ error }) {
  return (
    <html>
      <head>
        <title>Oh no!</title>
        <Meta />
        <Links />
      </head>
      <body className='m-4'>
        <h1 className='text-2xl'>Something went wrong!</h1>
        <p>{error.message}</p>
        <Scripts />
      </body>
    </html>
  );
}

Global Error handling

// my route component
export default () => {
  return (
    <div>...</div>
  )
}

// Error Boundery Component
export function ErrorBoundary({ error }) {
  return (
    <div>
      <h1>Error</h1>
      <p>{error.message}</p>
      <p>The stack trace is:</p>
      <pre>{error.stack}</pre>
    </div>
  );
}

Route Error handling

// We can throw a 404 error
export const loader = async ({ params }: LoaderArgs) => {
  const id = params.id;
  const category = await getDb().categories.findOne({ _id: new SafeObjectId(id) });
  if (!category) {
    throw json({ message: 'Resources not found' }, 404);
  }

  return json({ category });
};

Catch Error (http status errors)

// Status Catch Boundery
export function CatchBoundary() {
  const { data, status } = useCatch();
  return (
    <div>
      <p>Resources not found [{status} - {data.message}]</p>
    </div>
  );
}

When to use

Software Spectrum

So less code
faster UX
better DX

 

Is Remix
your
next
framework?

Want to learn more?

Made with Slides.com