Releases: seasonedcc/remix-forms
v3.0.0
Breaking changes
1. Only React Router v7 is supported
Remix Forms v3 removed support for Remix and React Router v6. Before upgrading Remix Forms, please upgrade your app to React Router v7.
The Remix team made upgrading easy, so you probably should. If you can't upgrade, Remix Forms 2.3.0
is stable and will continue working with React Router v6 and Remix.
2. createForm replaced by SchemaForm
Instead of using createForm
, you can now import SchemaForm
directly from remix-forms
.
Before:
import { createForm } from 'remix-forms'
// For Remix, import it like this
import { Form as FrameworkForm, useActionData, useSubmit, useNavigation } from '@remix-run/react'
// For React Router 6.4, like this
import { Form as FrameworkForm, useActionData, useSubmit, useNavigation } from 'react-router-dom'
const Form = createForm({ component: FrameworkForm, useNavigation, useSubmit, useActionData })
export { Form }
After:
import { SchemaForm } from 'remix-forms'
We stopped using the name Form
for our form component to avoid ambiguity with React Router's Form
component. We now refer to our forms as "schema forms".
3. createFormAction replaced by formAction
Instead of using createFormAction
, you can now import formAction
directly from remix-forms
.
Before:
import { createFormAction } from 'remix-forms'
// For Remix, import it like this
import { redirect, json } from '@remix-run/node'
// For React Router 6.4, like this
import { redirect, json } from 'react-router-dom'
const formAction = createFormAction({ redirect, json })
export { formAction }
After:
import { formAction } from 'remix-forms'
4. domain-functions replaced by composable-functions
We now depend on Composable Functions for our mutations. If you are a heavy Domain Functions user, please follow our complete migration guide. For most Remix Forms users, only a few simple changes are needed.
makeDomainFunction becomes applySchema
If you already have mutations created with makeDomainFunction
or mdf
, use applySchema
instead.
Before:
import { makeDomainFunction } from 'domain-functions'
const mutation = makeDomainFunction(schema)(async (values) => (
console.log(values) /* or anything else, like saveMyValues(values) */
))
After:
import { applySchema } from 'composable-functions'
const mutation = applySchema(schema)(async (values) => (
console.log(values) /* or anything else, like saveMyValues(values) */
))
environment becomes context
Composable Functions renamed environment
to context.
Before:
export const action: ActionFunction = async ({ request }) => {
return formAction({
request,
schema,
mutation,
environment: { customHeader: request.headers.get('customHeader') },
})
}
After:
export const action = async ({ request }: Route.ActionArgs) => {
return formAction({
request,
schema,
mutation,
context: { customHeader: request.headers.get('customHeader') },
})
}
Global errors with Error
Composable Functions requires us to throw an instance of Error for generating global errors. We cannot throw literal strings anymore.
Before:
const mutation = makeDomainFunction(schema)(async (values) => {
if (values.password !== 'supersafe') {
throw 'Wrong email or password'
}
return values
})
After:
const mutation = applySchema(schema)(async (values) => {
if (values.password !== 'supersafe') {
throw new Error('Wrong email or password')
}
return values
})
The second param to InputError is now an array
We now need to pass the field name as an array when throwing field errors.
Before:
const mutation = makeDomainFunction(schema)(async (values) => {
if (takenEmails.includes(values.email)) {
throw new InputError('Email already taken', 'email')
}
return values
})
After:
const mutation = applySchema(schema)(async (values) => {
if (takenEmails.includes(values.email)) {
throw new InputError('Email already taken', ['email'])
}
return values
})
5. PerformMutation type renamed to MutationResult
If you are using the PerformMutation
type, you'll need to change to MutationResult
from now on. The type is the same, only the name has changed.
6. formAction always returns the mutation result
Now we always return the full MutationResult
on formAction
. If you use formAction
without a successPath
and manually get the action data in your components, you'll have to check for success
first and only then access the mutation result inside data
:
export const action = async ({ request }: Route.ActionArgs) =>
formAction({ request, schema, mutation })
export default function Component({ actionData }: Route.ComponentProps) {
if (!actionData) {
return <p>Our action has not run yet.</p>
}
if (!actionData.success) {
return (
<div>
<h3>Errors:</h3>
<pre>{JSON.stringify(actionData.errors, null, 2)}</pre>
<h3>Values:</h3>
<pre>{JSON.stringify(actionData.values, null, 2)}</pre>
</div>
)
}
return (
<div>
<h3>Data:</h3>
<pre>{JSON.stringify(actionData.data, null, 2)}</pre>
</div>
)
}
7. formAction returns 422 status on failed mutations
To avoid revalidating loaders unnecessarily, formAction
now returns a 422 status on failed mutations.
8. Removed beforeAction and beforeSuccess callbacks
We removed the beforeAction
and beforeSuccess
callbacks from formAction
in favor of transformedResult
, described below.
9. Removed Callback type
Because we don't have callbacks anymore, we removed the Callback
type that was previously exported.
10. Removed deprecated parseActionData
We removed parseActionData
from our schema form component, which was marked as deprecated since v2.
11. onTransition renamed to onNavigation
SchemaForm
prop onTransition
was renamed to onNavigation
to keep up with React Router's new terminology.
Minor changes
12. Added transformResult
Instead of relying on callbacks, we now offer a more flexible transformResult
option to performMutation
and formAction
. It is a function that receives a MutationResult
and returns another result. Here's the type definition:
transformResult?: (
result: MutationResult<Schema, D>,
) => MutationResult<Schema, D> | Promise<MutationResult<Schema, D>>
A common use case is to make conditional redirects with custom headers, like:
export const action = async ({ request }: Route.ActionArgs) =>
formAction({
request,
schema,
mutation,
successPath: '/success',
transformResult: (result) => {
if (!result.success) {
session.flash('error', 'Invalid username/password')
throw redirect('/login', {
headers: { 'Set-Cookie': await commitSession(session) },
})
}
return result
},
})
The above example will throw
a redirect in case of failures and return the result otherwise.
13. successPath now accepts async functions
You can now pass an async function to successPath
on formAction
, like:
export const action = async ({ request }: Route.ActionArgs) =>
formAction({
request,
schema,
mutation,
successPath: async data => await calculateRedirectPath(data),
})
Full Changelog: v2.3.0...v3.0.0
v2.3.0
What's Changed
- Add navigate, fetcherKey, and unstable_flushSync props to Form by @danielweinmann in #240
Full Changelog: v2.2.1...v2.3.0
v2.2.1
What's Changed
- Ensure action data errors are not shown in fetcher forms by @danielweinmann in #234
Full Changelog: v2.2.0...v2.2.1
v2.2.0
v2.1.0
What's Changed
- Add emptyOptionLabel prop to Form by @danielweinmann in #223
Full Changelog: v2.0.0...v2.1.0
v2.0.0
🔥 Breaking changes
- We now depend on domain-functions
2.x
.
What's Changed
- Upgrade domain-functions to v2 by @danielweinmann in #222
Full Changelog: v1.6.6...v2.0.0
v1.6.6
What's Changed
- Fix domain-functions peer dependency to avoid breaking changes by @danielweinmann in #220
Full Changelog: v1.6.5...v1.6.6
v1.6.5
What's Changed
- Add
replace
prop toForm
component by @kaciakmaciak in #201 - Configure linter and add it to CI by @danielweinmann in #208
- Bump turborepo to 1.10 by @felipefreitag in #209
- Do not add click handlers to button for type reset or button by @felipefreitag in #210
- Simplify and upgrade ci actions by @felipefreitag in #211
- Fix client-side submits when button has children elements by @danielweinmann in #215
New Contributors
- @kaciakmaciak made their first contribution in #201
Full Changelog: v1.6.4...v1.6.5
v1.6.4
What's Changed
- Improve condition for disabled state by @danielweinmann in #206
- Use button label as default for pending button label by @danielweinmann in #207
Full Changelog: v1.6.3...v1.6.4
v1.6.3
What's Changed
- Remove useTransition from getting started by @danielweinmann in #186
- Add compatibility with Remix v2_normalizeFormMethod by @danielweinmann in #187
Full Changelog: v1.6.2...v1.6.3