iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🧩

Exploring React Router v7 Data Mode and Conform's future features

に公開

What is this?

This is a verification article where I actually tried the data mode (loader/action) of React Router v7. Assuming I will use it in a self-made browser extension in the future, I tried implementing it with Memory Router, and also tried the future feature of Conform as well. Since I hadn't used data mode before, I wanted to check the basic behavior first.

The working demo is published at the following URL:
https://data-router-test.vercel.app/

The source code is published in the following repository:
https://github.com/coji/data-router-test

Motivation: I wanted to try Data Mode

I have plans to create a browser extension and wanted to use React Router v7. However, since I had never used data mode (loader/action) in the first place, I created a verification project to understand the basic behavior first.

In browser extensions, you cannot rely on the URL bar, so the implementation should use Memory Router. Therefore, I decided to check if data mode works correctly with Memory Router this time.

Setting up the Verification Project

Assuming use in a browser extension, I created a project for verification.

pnpm create vite@latest data-router-test --template react-ts
cd data-router-test
pnpm install

Install the necessary packages.

# React Router v7
pnpm add react-router@7

# Conform + Zod (using future feature)
pnpm add @conform-to/react@1.11.0 @conform-to/zod@1.11.0 zod@4

# UI related
pnpm add sonner tailwindcss@4 @tailwindcss/vite

Implementation using Memory Router

Since browser extensions require routing independent of the normal browser's URL, we use createMemoryRouter.

Router Configuration (routes.tsx)

import { createMemoryRouter } from 'react-router'
import App, { loader } from './routes/_index/route'
import Layout from './routes/_layout'
import Form, { action } from './routes/form/route'

export const router = createMemoryRouter([
  {
    Component: Layout,
    children: [
      {
        path: '/',
        loader,
        Component: App,
      },
      {
        path: '/form',
        Component: Form,
        action,
      },
    ],
  },
])

Entry Point (main.tsx)

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router'
import { Toaster } from '~/components/ui/sonner'
import './index.css'
import { router } from './routes'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <Toaster richColors closeButton />
    <RouterProvider router={router} />
  </StrictMode>,
)

Implementation of Data Mode

Data Fetching using Loader

The key to data mode is the ability to fetch data before component rendering.

export const loader = async ({ request }: LoaderFunctionArgs) => {
  // API call or data fetching from storage
  const data = await fetchSomeData()
  return { data }
}

export default function HomePage() {
  const { data } = useLoaderData<typeof loader>()
  // Render with data guaranteed
  return <div>{data.message}</div>
}

Form implementation using Conform's future feature

I tried a simpler form implementation using the future feature introduced in Conform v1.11.

Key points of the future feature

With the future feature, new APIs are available. In particular, the combination of parseSubmission and report has made error handling dramatically simpler.

// Import from the future path
import { parseSubmission, report, useForm } from '@conform-to/react/future'
import { coerceFormValue } from '@conform-to/zod/v4/future'

// Schema definition (type coercion with coerceFormValue)
const schema = coerceFormValue(
  z.object({
    name: z.string({ error: 'Name is required' }),
  }),
)

// Process form submission in the Action function
export const action = async ({ request }: ActionFunctionArgs) => {
  const submission = parseSubmission(await request.formData())
  const result = schema.safeParse(submission.payload)

  if (!result.success) {
    // Return errors using report
    return { lastResult: report(submission, { error: result.error }) }
  }

  // Clear the form with reset on success
  return { lastResult: report(submission, { reset: true }) }
}

// Component side
export default function FormPage() {
  const actionData = useActionData<typeof action>()
  const { form, fields, intent } = useForm({
    lastResult: actionData?.lastResult,
    schema,
  })

  return (
    <Form method="POST" {...form.props}>
      <input name={fields.name.name} />
      <div>{fields.name.errors}</div>
      <button type="submit">Submit</button>
      <button onClick={() => intent.reset()}>Reset</button>
    </Form>
  )
}

Implementation Points

Memory Router × Data Mode

The combination of Memory Router and data mode is ideal for browser extension development. It allows you to benefit from loader/action while having routing that is independent of the URL bar.

Power of Conform's future feature

Compared to traditional Conform, the future feature excels in the following points:

  • parseSubmission: FormData analysis is completed in one line
  • report function: Unified state management for errors and resets
  • coerceFormValue: Automates type conversion (e.g., "123" → 123)
  • intent.reset(): Form resetting is achieved with a single method

Image of use in browser extensions

When actually creating a browser extension, the combination with the chrome.storage API seems like it will be powerful.

// Fetch data from storage in the loader
export const loader = async () => {
  const { settings } = await chrome.storage.local.get(['settings'])
  return { settings: settings || {} }
}

// Save to storage in the action
export const action = async ({ request }: ActionFunctionArgs) => {
  const formData = await request.formData()
  await chrome.storage.local.set({
    settings: Object.fromEntries(formData)
  })
  return { success: true }
}

Summary

Through this verification, I confirmed that the data mode of React Router v7 can be used without problems in browser extension development. By using Memory Router, you can develop in the usual React Router style without relying on the URL bar.

Also, Conform's future feature was simpler than I imagined. Especially the combination of parseSubmission and report significantly reduces the boilerplate for error handling.

Next time, I'd like to try actually creating a browser extension. It's really convenient to be able to use the tools you're normally accustomed to.

GitHubで編集を提案

Discussion