Remix: Data mutations using forms and actions

Let’s add a little form to add a new blog post.

There will be no authentication here, so anyone can add a blog post.

Not something recommended to have on a public site of course, but will work for our sample app to demonstrate mutations.

Create a new route component in app/routes/blog.new.jsx so it will “respond” on the URL /blog/new.

You can link to it in the component from app/routes/blog.jsx:

//...
        <ul className='list-disc list-inside row-span-1'>
          {posts.map((post) => (
            <li key={post.id}>
              <NavLink
                to={`/blog/${post.id}`}
                className={({ isActive }) => (isActive ? 'font-bold' : '')}>
                {post.title}
              </NavLink>
            </li>
          ))}
          <li>
            <NavLink
              to='/blog/new'
              className={({ isActive }) => (isActive ? 'font-bold' : '')}>
              New post
            </NavLink>
          </li>
        </ul>
//...

Now in app/routes/blog.new.jsx start by creating a component:

export const meta = () => {
  return [{ title: 'New post' }]
}

export default function NewPost() {

  return (
    //...
  )
}

Now we create a form using the Form component that’s provided by Remix:

import { Form } from '@remix-run/react'

export const meta = () => {
  return [{ title: 'New post' }]
}

export default function NewPost() {
  return (
    <Form method='post'>
      <label>
        Title: <input className='border' name='title' required='true' />
      </label>
      <button type='submit' className='p-2 bg-zinc-300 border'>
        Create post
      </button>
    </Form>
  )
}

When the form is submitted, Remix executes the action on the route.

So we add it by adding a named function export action.

In there we first get the post title from the form data:

export async function action({ request }) {
  const formData = await request.formData()
  const title = formData.get('title')

  //...
}

Then we insert this new post in the database, and we redirect to it:

import { redirect } from '@remix-run/node'
import { getDb } from '../database.server.js'

//...

export async function action({ request }) {
  const formData = await request.formData()
  const title = formData.get('title')

  const db = await getDb()
  const result = await db.run('INSERT INTO posts (title) VALUES (?)', title)
  await db.close()

  return redirect(`/blog/${result.lastID}`)
}

Here’s the full code of the form fully working:

import { Form } from '@remix-run/react'
import { redirect } from '@remix-run/node'
import { getDb } from '../database.server.js'

export const meta = () => {
  return [{ title: 'New post' }]
}

export async function action({ request }) {
  const formData = await request.formData()
  const title = formData.get('title')

  const db = await getDb()
  const result = await db.run('INSERT INTO posts (title) VALUES (?)', title)
  await db.close()

  return redirect(`/blog/${result.lastID}`)
}

export default function NewPost() {
  return (
    <Form method='post'>
      <label>
        Title: <input className='border' name='title' required='true' />
      </label>
      <button type='submit' className='p-2 bg-zinc-300 border'>
        Create post
      </button>
    </Form>
  )
}

Lessons in this unit:

0: Introduction
1: Create your first Remix app
2: The root route
3: File based routing
4: Linking your pages
5: Styling with CSS and Tailwind
6: Create a navigation
7: Dynamic routes and nested routes
8: Connecting a database
9: ▶︎ Data mutations using forms and actions
Are you intimidated by Git? Can’t figure out merge vs rebase? Are you afraid of screwing up something any time you have to do something in Git? Do you rely on ChatGPT or random people’s answer on StackOverflow to fix your problems? Your coworkers are tired of explaining Git to you all the time? Git is something we all need to use, but few of us really master it. I created this course to improve your Git (and GitHub) knowledge at a radical level. Launching May 21, 2024. Join the waiting list!