Let's review the setup and starting point. I'm using React Router v7 here, then tailwind for CSS and a local db with sqlite. I'm using Prisma to interact with the database.
Be in fullscreen version.
Show the app. We have a list of jokes, a form to add new jokes, and a way to navigate. View jokes, get random joke, add new link, create + validate (funny joke), delete, favorite.
Go to split view.
We recognize the boilerplate from the slides. We have a vite.config.ts
, react-router.config.jsfile, a
tsconfig.json` file. Prisma stuff and public folder. Eslint and prettier are also set up.
An app
folder, and i revealed the entry.server and entry.client files. Here are components and the routes.
Let's start with the routes folder. I'm using file-based routing here and my RRM's are defined as files, and we have to follow a certain convention. THis is called flat routes, we define routes with dot and other chars. Maybe you like it, maybe you don't. Common in most meta-frameworks, but with very different rules. There is also a code-option as mentioned. Show routes.ts file to define code based routing. Route modules are code split so we can lazily load js as we navigate to new routes for better performance.
The .react-router folder here is autogenerated to provide type safety to our routes which is a very new feature that is currently being improved.
Start with root.tsx. This is the root route module. It has a special Layout export' returning the root document with the metadata and links that are collected from link functions and meta functions for route modules, and inserted into the head. It's then returning the children of the layout, which is the default export, App.
App has a Outlet component, the outlet is for defining this routes children routes, using nested layouts here. This allows us to build complex UIs. And remix can fetch data for all nested routes in parallel.
Error boundaries for each route module using useRouteError. Here we catch any error that might occur. We can show a custom error message here. We can also use the error hook to get the error and show it in the UI.
Lets look at index.tsx. Default/index child of the root.tsx. Showcase in app the page. Use the web! Central part of Remix. Remix feels familiar to old school devs. Less react-y. Learning the web when learning Remix. This is a Remix Route Module for the index page. It's using the Remix Link component to provide client side navigation.
It's also defining metadata to be collected for the head with this meta function. Can see it in the title "Remix Jokes".
Index.tsx. Here I'm using tailwind, but Remix also has this feature called links. This is a way to solve performance issues with CSS. You can use links to load CSS only when needed. It defined a linkFunction that returns a link to the CSS file. Not using it here.
Lets move to the rest of the files. Jokes.tsx is a relative route. Go to jokes in demo. Another route module for the url /jokes. This route module has a loader! Fetching data from the database - a list of jokes. It's using Prisma to interact with the database. It's using the loader to fetch the data and return it to the component using the loaderData prop and type safe Route.ComponentProps. Importing autogenerated types from .react router to get type safe loader. We don't need useEffect fetching, kind of like getServerSideProps in Next.js.
It's defining lots of UI from this default component. A header, a sidebar with jokes and Links, and mapping jokes to link with prefetch. If prefetched, we don't see this indicator.
Further down there is a Outlet component and a footer, the outlet is for defining this routes children routes, using nested layouts here again.
The index route is the default child of the jokes route, and is rendered in the outlet on the route. It's using a loader to fetch a random joke. Throws a 404 if no joke is found.
Here we catch the error that might occur when posting a new joke to the db. We can show a custom error message here. We can also use the error hook to get the error and show it in the UI. Here catching a 404 error to display a custom message with a link to the jokes page.
Relative route. Lets move to the jokes.new.tsx route module. Default export has a form, capital letter. This is similar to our example from the slides. Remix Form component will provide extra features on top of default form component, such as client side nav avoiding full page reload.
There is a method post on this, which will call the route for the route module with its action. The action validates using zod and returns errors if invalid inside a badRequest wrapper. Customizable. Then it redirects. Show console of network tab to show the post and get requests. GET data joke.new. POST data joke.new with location in the equest. New GET for revalidation.
We don't need api routes to talk to our server from the client. Route module is it's own API route talking to itself.
Data is automatically revalidates. No fuss with adding items to lists. It's always the same. No manual revalidation. It's always in sync with the server. We can push or filter lists client side.
Show network tab loading and revalidating.
Lets move to a more complex route module with lots of features.
Go to joke.$jokeid.tsx. This is a dynamic route with params. Show it in the app. It's a route module for a single joke. It's using the loader to fetch the data for the joke with the id from the database. It's using the loaderData prop to pass the data to the component, and throw a 404 if no joke is found.
It's also using the useRouteError hook to catch any errors that might occur when fetching the data. It's using the error hook to get the error and show it in the UI and handle 404 errors differently.
JokeDisplay can delete a joke, but this is now a relative URL using a action="/destroy". Using a form as well which is unfamiliar for React devs who usually just do button onClicks. This is a relative URL that will call the route module with the action destroy. Defined with file based routing. It's using the action to delete the joke from the database, and redirect. Web standard stuff that at least for me is very unfamiliar as a React SPA dev originally. Delete checks for the intent to be expected. This can be used to handle multiple actions in the same route module. Then deletes from the db and redirects.
It can also be favorited. Maybe a bit weird to see a form for this. This triggers the relative URL action="/". This is a relative URL that will call the route module with the action favorite. Here we use a fetcher Form because we are not navigating anywhere and we don't want to trigger a new push to the router.
The app has some, realistic added delay and that makes it not feel so good. And until we didn't actually use that much of the browser framework, only Form and Links and prefetching etc. Does this feel like React? We are able to create all this without any React-stuff. Amazing right?
Let's switch to another version of the app that has some UI enhancements using more of the browser framework of Remix.
In the jokes.tsx, we will use a Remix specific hooks around here which is the browser framework. This one is called useNavigation. It will give you the current state of the navigation, and provide i.e whether the router is navigating, which we use here to mark the page as pending with css. This hook adds on top of our base case HTML web standard document with additional client-side, js enhanced features. This is Remix's way to do progressive enhancement.
Delete is enhanced with a disabled button again when the navigation is not idle and is on the intent "delete".
For the favorite, we can get the state of this fetcher form locally and do another optimistic update. Fetcher is local scoped to the component.
It's doing optimistic UI by returning a view of a JokeDisplay if the validation succeeds. This is a client side update that will show the joke in the list before the server has responded.
Go to fullscreen app.
Turn off JS and see that the app still works. Web fundamentals in play to navigate, submit forms. Just lacking the client side enhancement of prefetching and client side navigation. And no client side loading state. And optimistic updates. Can even favorite because it's a form.
Can return errors from the form and delete jokes. But we don't get client side pending state or optimistic updates. Thats all a progressive enhancement of this base case web standard application.
We still didn't use any useState or useEffect here. But we can of course add it to our relevant components without any problem in the future. You know how to do that! Remix solves problems with RRM and additional hooks. Can integrate any libs like normal for more react stuff. Feels native to web.