withFormSubmitHandler
extensionInstallation
npx jsrepo add github/reatom/reusables withFormSubmitHandler Copy the source code below and save it to the specified file path in your project.
import { action, type Action, type Form } from '@reatom/core'
interface PreventableEvent {
preventDefault(): void
}
interface FormSubmitHandlerConfig {
/**
* Skip submission when the form is not dirty.
*
* - `true` — silently skip
* - `string` — skip and set the string as `form.submit.error`
*/
requireDirty?: true | string
}
interface FormSubmitHandlerExt {
handleSubmit: Action<[event?: PreventableEvent], void>
}
/**
* Form extension that adds a `handleSubmit` action for event-driven submission
* with an optional dirty-guard.
*
* @example
* const form = reatomForm(
* { name: '' },
* { onSubmit: async (s) => api.save(s) },
* ).extend(withFormSubmitHandler({ requireDirty: true }))
*
* // In a framework handler:
* form.handleSubmit(event)
*/
export const withFormSubmitHandler =
({ requireDirty }: FormSubmitHandlerConfig = {}) =>
<T extends Form<any>>(form: T): FormSubmitHandlerExt => ({
handleSubmit: action((event?: PreventableEvent) => {
event?.preventDefault()
if (requireDirty && !form.focus().dirty) {
if (typeof requireDirty === 'string') {
form.submit.error.set(new Error(requireDirty))
}
return
}
form.submit()
}, `${form.name}.handleSubmit`),
}) Documentation
withFormSubmitHandler
Adds a handleSubmit action to a form for event-driven submission with an optional dirty-guard.
The problem
Submitting a Reatom form from a UI event handler requires boilerplate: calling event.preventDefault(), optionally checking the dirty state, and then calling form.submit(). This boilerplate is repeated in every form component and is easy to get wrong (e.g. forgetting preventDefault or the dirty check).
withFormSubmitHandler encapsulates this pattern into a single handleSubmit action that works with any framework event (DOM, React, Svelte, etc.).
withFormSubmitHandler(config?)
Creates a form extension that adds a handleSubmit action.
The action:
- calls
event.preventDefault()if an event is passed - when
requireDirtyis set and the form is not dirty, skips submission (silently or with an error) - calls
form.submit()fire-and-forget (useform.submit.onFulfill/form.submit.onRejectfor async tracking)
Parameters
| Parameter | Type | Description |
|---|---|---|
requireDirty |
true | string |
true — silently skip when not dirty; string — skip and set form.submit.error |
Returns
Form extension that adds handleSubmit: Action<[event?: { preventDefault(): void }], void> to the form.
Example
import { reatomForm } from '@reatom/core'
import { withFormSubmitHandler } from '#reatom/extension/with-form-submit-handler'
const form = reatomForm(
{ name: '', email: '' },
{
onSubmit: async (state) => {
await api.save(state)
},
},
).extend(withFormSubmitHandler({ requireDirty: true }))
Framework integration
React
<form onSubmit={form.handleSubmit}>{/* fields */}</form>
Svelte
<form on:submit={form.handleSubmit}>
<!-- fields -->
</form>
Vanilla DOM
document.querySelector('form')!.addEventListener('submit', form.handleSubmit)
See also "Reatom extensibility saves the day" for more on the extension pattern.
Example
import { reatomForm } from '@reatom/core'
import { withFormSubmitHandler } from './with-form-submit-handler'
// Silent skip when not dirty
const silentForm = reatomForm(
{ name: '' },
{
onSubmit: async (state) => {
console.log('Submitted', state)
},
},
).extend(withFormSubmitHandler({ requireDirty: true }))
silentForm.handleSubmit() // silently skipped (not dirty)
silentForm.fields.name.change('Ada')
silentForm.handleSubmit() // submits (dirty)
// Error when not dirty
const errorForm = reatomForm(
{ name: '' },
{
onSubmit: async (state) => {
console.log('Submitted', state)
},
},
).extend(withFormSubmitHandler({ requireDirty: 'No changes to save' }))
errorForm.handleSubmit() // sets form.submit.error
console.log(errorForm.submit.error()?.message) // "No changes to save"
errorForm.fields.name.change('Ada')
errorForm.handleSubmit() // submits (dirty)