Remix: Finishing the tutorial

Andrew Bone - Feb 13 '22 - - Dev Community

Yesterday I started a new series to follow my journey into learning Remix, we got about halfway through the blog tutorial which meant we had routes and could load posts dynamically from the file system. Today we're going to work through the rest of the tutorial.

Making an admin section

The tutorial barrels on having us use many things we've already learnt to create a new route and populate it with data.

CSS and styles

I was quite surprised that this section very quickly moved on to how to link CSS files to your front end, not that isn't something we need but rather I'd have thought it would be something introduced much earlier.

The process is similar to how you would create a CSS file for a single page app. You create a styles directory, though the name isn't important, inside the app directory. Within the newly created styles directory you make a CSS file, we'll call ours admin.css.

To import our CSS file we use another magic function, just like the loader function from last time.

import adminStyles from "~/styles/admin.css";

export const links = () => {
  return [{ rel: "stylesheet", href: adminStyles }];
};
Enter fullscreen mode Exit fullscreen mode

As you can see we import our CSS and then return it inside an array from our links function. Because I knew the loader function took props I decided to console log out any props that the link function may have been getting. Sadly, all I got was undefined but no harm in looking.

As an aside, whilst I love CSS I know CSS-in-JS solutions and SASS/LESS are widely used and I prefer them myself. The tutorial doesn't go into how to use these things but a quick google showed me it was indeed possible.

Nesting routes with an outlet

The tutorial advices us to "Hang with [them]" at the start of this section as we have to unlearn a pattern that was asserted earlier on.

We have been lead to believe that, in the routes folder, we can make a file like url.tsx and if you go to /url our react code will load. We have also learnt that we can make a directory called url with a file inside it called index.tsx to achieve the same end. Both of these statements are true however we were also taught that url.tsx would supersede url/index.tsx but this is not entirely true.

Whilst it is true that, out of the box, url.tsx would be displayed we can use Outlet, imported from remix to display url/index.tsx or, in fact, anything inside the url directory as a nested component. This can be a little confusing at first but is helpful for navs, let's make a silly example.

/* our nav file called sites.tsx */
import { Outlet, Link } from "remix";

export default function Site() {
  return (
    <>
      <nav style={{ display: "flex", gap: "1rem", justifyContent: "center" }}>
        <Link to="/sites/dev">Dev.to</Link>
        <Link to="/sites/twitter">Twitter</Link>
        <Link to="/sites/facebook">Facebook</Link>
      </nav >
      <Outlet />
    </>
  );
}

/** 
 * our individual files called /sites/dev.tsx, 
 * /sites/twitter.tsx and /sites/facebook.tsx
 */
export default function Dev() {
  return <h1 style={{ textAlign: "center" }}>I love Dev.to</h1>;
}
export default function Twitter() {
  return <h1 style={{ textAlign: "center" }}>I like twitter</h1>;
}
export default function Facebook() {
  return <h1 style={{ textAlign: "center" }}>I tolerate facebook</h1>;
}
Enter fullscreen mode Exit fullscreen mode

gif. clicking between nested components

As you can see the content of the page changes to match the sub page, as does the URL, but the nav from site.tsx is shown first allowing us to have multiple pages with differing content surrounded by a single wrapper.

Actions

Actions, it seems, are a way to send data back to the server akin to a post request. In fact, as we'll soon learn, it is just that. A post request.

Form Element

To start with we need a form element, not too dissimilar to PHP development, rather than a standard form element though this is one imported from remix, we wrap our inputs and submit button with the imported <Form> which we can give a method. The example method we're given with this tutorial is POST so that's what we'll use.

When we fill out the form we get an error, this is because we haven't got our magic function set up yet. Surprisingly, at least to me, our site doesn't try and post anything when we press submit we just get an error in the console.

Error: Route "routes/admin/new" does not have an action, but you are trying to submit to it.
Enter fullscreen mode Exit fullscreen mode

Another magic function

This is our third and final magic function for this tutorial, we've had loader, links and now action. Just like the loader function from last time action receives a request object, a context something and a params object.

We can get our form data from the request and then get each string based on the name of the input. We can do our error handling inside the action function and, if there is an error, return an object of errors.

If the action functions returns successfully you can redirect the user back to the admin section or even to the post they've just created.

Error and Loading hooks

The two hooks we're going to use are; useActionData for the errors and useTransition for the loading state.

useActionData

As you may have guess, though there's nothing harm if this is news to you, useActionData is not a hook solely for error handling. It is, however, a hook for looking at what is returned from our action function.

If you recall earlier I said we can return an object of errors. In the tutorial the way that works is if any of the form data is blank it will return an object, halting the request, the object will contain a key for any field that is blank with the value true. For instance,

{
  title: true,
  markdown: true
}
Enter fullscreen mode Exit fullscreen mode

The above object is what useActionData will return. Meaning we can conditionally render warnings based on what the object contains.

useTransition

The useTransition function returns an object with 4 keys; state, submission, location, type.

  • state - is a string
  • submission - is undefined or is an object
  • location - is undefined or is an object
  • type - is a string

In the tutorial we use submission, which is undefined when nothing is happening, to conditionally change the text inside our button to give some feedback to the user.

The end

Well that's the end of the tutorial, we've made a system that can dynamically read markdown in order to populate its navigation and also a way to send data from a form back to the server in order to write new markdown files.

What would you like to see next? I have a couple of options that I think are possible from here. I can either take what we have so far and make it look nicer, maybe with styled-components, and maybe even hook it up to a database rather than using the file system. Or I can make something completely different but trying to use the patterns we've learnt so far.

Either way the next project won't be a tutorial follow along it will be me coding and sharing what I've learnt along the way. I think I'll keep all the code on github too, unlike this project that I kept local.

If you'd like to connect with me outside of Dev here are my twitter and linkedin come say hi 😊.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .