iTranslated by AI
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:
The source code is published in the following repository:
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.
Discussion