25
loading...
This website collects cookies to deliver better user experience
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.
npx create-remix@latest
# follow the prompts
cd [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.
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
You can use yarn for the steps below if you prefer
npm run dev
# Start the Remix development asset server
$ npm run dev
# In a new tab start your express app:
npm run start:dev
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
.
# Remove demo files
rm -rf app/routes/demos app/styles/demos
# We'll recreate this files later
rm app/routes/index.tsx app/root.tsx
hello world
example.// app/root.tsx
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
</head>
<body>
<h2>Hello World</h2>
</body>
</html>
);
}
// app/root.tsx
import {Links,LiveReload,Meta,Outlet,Scripts,ScrollRestoration} from "remix";
export default function App() {
return (
<Document>
<Layout>
<Outlet />
</Layout>
</Document>
);
}
// Here is the blueprint of our document
// It looks like our typical HTML but with a few extra tags
// I will discuss in another blog post those Components coming from the remix package
function Document({
children,
title,
}: {
children: React.ReactNode;
title?: string;
}) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{title ? <title>{title}</title> : null}
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
);
}
// Layout is a wrapper component that provides a consistent layout for all pages.
function Layout({ children }: React.PropsWithChildren<{}>) {
return <main>{children}</main>;
}
// app/routes/index.jsx
export default function Index() {
return <div>
<h2>Hello World</h2>
</div>
}
Layout
since it will be reusable across all pages.// app/root.tsx
import {Link /*other import*/} from "remix";
// ...
function Layout({children}: React.PropsWithChildren<{}>) {
return (
<>
<header>
<nav>
<ul>
<li>
<Link to="/vocab">Vocab</Link>
</li>
</ul>
</nav>
</header>
<main>{children}</main>;
</>
);
}
// ...
/pokemons
page.CatchBoundary
and useCatch
to create a custom 404
error message as a fallback for all Not Found routes.// app/root.tsx
import { useCatch /*other imports*/ } from "remix";
// ...
export function CatchBoundary() {
let caught = useCatch();
let message;
switch (caught.status) {
case 404:
message = <p>This is a custom error message for 404 pages</p>
break;
// You can customize the behavior for other status codes
default:
throw new Error(caught.data || caught.statusText);
}
return (
<Document title={`${caught.status} ${caught.statusText}`}>
<Layout>
<h1>
{caught.status}: {caught.statusText}
</h1>
{message}
</Layout>
</Document>
);
}
// ...
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".
404
error, let's create the /pokemons
route// app/routes/pokemons/index.tsx
export default function Pokemons() {
return (
<div>
<h2>Pokemons</h2>
</div>
);
}
// app/routes/pokemons/index.tsx
// This is another "contract function"
export function meta() {
return {
title: 'Pokemons',
description: 'List of Pokemons',
}
}
// export default function Pokemons...
loader
// app/routes/pokemons/index.tsx
import type { LoaderFunction } from "remix"
// This is another "contract function"
export const loader: LoaderFunction = () => {
return fetch("https://pokeapi.co/api/v2/pokemon")
}
// export default function Pokemons...
.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.
useLoaderData
hook to access the data in React land.// app/routes/pokemons/index.tsx
import { useLoaderData, Link /*other imports*/ } from 'remix'
// export let loader: LoaderFunction...
export default function Pokemons() {
const data = useLoaderData()
// Try to use console.log here
return (
<div>
<h2>Pokemons</h2>
<ul>
{data.results.map(pokemon => (
<li key={pokemon.name}>
<Link to={`/pokemons/${pokemon.name}`}>{pokemon.name}</Link>
</li>
))}
</ul>
</div>
)
}
pokemons
folder, create a folder named $pokemonName.tsx
.// app/routes/pokemons/$pokemonName.tsx
export default function Pokemon() {
return (
<div>
<h1>Specific Pokemon Route</h1>
</div>
);
}
$pokemonName.tsx
, inside the file, we can access pokemonName
inside the params
object.see line #9
// app/routes/pokemons/$pokemonName.tsx
import { useLoaderData } from "remix"
import type { LoaderFunction } from "remix"
export let loader: LoaderFunction = async ({ params }) => {
const pokemonName = params.pokemonName;
// OR const { pokemonName } = params;
const details = await fetch(
`https://pokeapi.co/api/v2/pokemon/${pokemonName}`
).then((res) => res.json());
// We'll map the data based on our needs
return {
name: pokemonName,
weight: details.weight,
img: details.sprites.front_default,
id: details.id,
};
};
export default function Pokemon() {
const pokemon = useLoaderData();
return (
<div>
<h1>
{pokemon.name} #{pokemon.id}
</h1>
<img src={pokemon.img} alt={pokemon.name} />
<p>Weight: {pokemon.weight}</p>
</div>
);
}
// app/routes/pokemons/$pokemonName.tsx
import type { MetaFunction } from "remix"
// You can access the `loader` data here
export const meta: MetaFunction = ({ data }) => {
return {
title: `#${data.id} ${data.name}`,
description: `Details of ${data.name}`,
};
}
hooks
to more conveniently work with the data, errors, etc. Having said that, I definitely will explore more