Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/reatom/reatom/llms.txt

Use this file to discover all available pages before exploring further.

Overview

reatomForm creates a reactive form with built-in validation, submit handling, and comprehensive field management. It wraps reatomFieldSet and adds form-level features like submit actions, schema validation, and submitted state tracking.

Type Signature

function reatomForm<
  InitState extends FieldSetInitState,
  SchemaState = unknown,
  SubmitReturn = unknown,
  SubmitParams extends any[] = any,
>(
  initState: InitState | ((name: string) => InitState),
  options?: FormOptionsWithSchema | FormOptionsWithoutSchema
): FormAtom<InitState, SchemaState, SubmitReturn, SubmitParams>

Parameters

initState
InitState | ((name: string) => InitState)
required
Initial form state or factory function. Can include:
  • Primitive values (automatically converted to reatomField)
  • reatomField instances with custom options
  • reatomFieldArray instances for dynamic lists
  • Nested objects for complex form structures
options
FormOptions
Form configuration options

Return Value

FormAtom
FormAtom<InitState, SchemaState, SubmitReturn, SubmitParams>
Form atom with the following properties:

Examples

Basic Form

import { reatomForm } from '@reatom/framework'

const loginForm = reatomForm({
  email: '',
  password: '',
})

// Access fields
loginForm.fields.email.change('user@example.com')
loginForm.fields.password.change('secret123')

// Check form state
console.log(loginForm.fields.email.value()) // 'user@example.com'
console.log(loginForm.focus().dirty) // true

Form with Schema Validation

import { reatomForm } from '@reatom/framework'
import { z } from 'zod'

const registrationForm = reatomForm(
  {
    email: '',
    age: 0,
    password: '',
  },
  {
    schema: z.object({
      email: z.string().email('Invalid email'),
      age: z.number().min(18, 'Must be 18 or older'),
      password: z.string().min(8, 'Password too short'),
    }),
    onSubmit: async (state) => {
      // state is typed as { email: string; age: number; password: string }
      const response = await fetch('/api/register', {
        method: 'POST',
        body: JSON.stringify(state),
      })
      return response.json()
    },
  }
)

// Submit with error handling
await wrap(registrationForm.submit()).catch(noop)
if (registrationForm.submit.error()) {
  console.error('Submission failed:', registrationForm.submit.error())
}

Field-Level Validation

import { reatomForm, reatomField } from '@reatom/framework'

const form = reatomForm({
  username: reatomField('', {
    validate: ({ value }) => {
      if (value.length < 3) return 'Username too short'
      if (!/^[a-zA-Z0-9_]+$/.test(value)) return 'Invalid characters'
    },
    validateOnChange: true,
  }),
  email: reatomField('', {
    validate: async ({ value }) => {
      // Async validation
      const response = await fetch(`/api/check-email?email=${value}`)
      const { available } = await response.json()
      if (!available) throw new Error('Email already taken')
    },
    validateOnBlur: true,
  }),
})

Dynamic Field Arrays

import { reatomForm, reatomField, reatomFieldArray } from '@reatom/framework'

const todoForm = reatomForm({
  title: '',
  items: reatomFieldArray({
    initState: ['Initial task'],
    create: (text: string) => reatomField(text),
  }),
})

// Add items
todoForm.fields.items.create('Buy groceries')
todoForm.fields.items.create('Walk the dog')

// Access items
const items = todoForm.fields.items.array()
console.log(items.length) // 3
console.log(items[0].value()) // 'Initial task'

// Remove items
todoForm.fields.items.clear()

Submit Flow

import { reatomForm } from '@reatom/framework'
import { z } from 'zod'

const form = reatomForm(
  { email: '', message: '' },
  {
    schema: z.object({
      email: z.string().email(),
      message: z.string().min(10),
    }),
    validateBeforeSubmit: (state) => {
      // Custom validation after schema
      if (state.message.includes('spam')) {
        throw new Error('Message contains spam')
      }
    },
    onSubmit: async (state) => {
      // 1. All field validations are triggered
      // 2. Schema validation runs
      // 3. validateBeforeSubmit runs
      // 4. This callback runs if all validations pass
      const response = await fetch('/api/contact', {
        method: 'POST',
        body: JSON.stringify(state),
      })
      return response.json()
    },
  }
)

// Submit returns a promise
const result = await wrap(form.submit())

// Access submission state
console.log(form.submitted()) // true
console.log(form.submit.data()) // Last successful result
console.log(form.submit.error()) // Last error (if any)

Submit with Custom Parameters

const form = reatomForm(
  { email: '' },
  {
    onSubmit: async (state, skipDebounce: boolean) => {
      if (!skipDebounce) await wrap(sleep(300))
      return { state, skipDebounce }
    },
  }
)

// Pass custom parameters to submit
const result = await wrap(form.submit(true))
console.log(result) // { state: { email: '' }, skipDebounce: true }

Autofocus First Error Field

import { reatomForm } from '@reatom/framework'
import { withCallHook } from '@reatom/framework'

const form = reatomForm(
  { email: '', age: 0 },
  {
    schema: z.object({
      email: z.string().email(),
      age: z.number().min(18),
    }),
  }
)

// Focus first field with error on submit failure
form.submit.onReject.extend(
  withCallHook(() => {
    const errorField = form
      .fieldsList()
      .find((field) => !!field.validation().error)
    errorField?.elementRef()?.focus()
  })
)

Nested Forms

const form = reatomForm({
  personal: {
    firstName: '',
    lastName: '',
    age: 0,
  },
  address: {
    street: '',
    city: '',
    country: '',
  },
})

// Access nested fields
form.fields.personal.firstName.change('John')
form.fields.address.city.change('New York')

// Get form state
const state = form()
// {
//   personal: { firstName: 'John', lastName: '', age: 0 },
//   address: { street: '', city: 'New York', country: '' }
// }

Submit Flow

When submit() is called, the following sequence occurs:
  1. Field Validation: All fields in fieldsList have their validation triggered
  2. Schema Validation: If a schema is provided, it validates the entire form state
  3. Custom Validation: The validateBeforeSubmit callback runs (if provided)
  4. Submit Handler: The onSubmit callback executes with validated state
  5. Submitted State: The submitted atom is set to true
  6. Reset (optional): If resetOnSubmit is true, the form resets to initial state
If any step throws an error, the flow stops and the error is available via submit.error().