Skip to content

Quickstart

IMPORTANT

This library is still in early development and the API might change before the first stable release.

This is a quickstart guide to get you started with the Form Signals React bindings. If you want to use a different UI library, check out the other quickstart guides.

Installation

To install the package, run:

bash
npm install @formsignals/form-react @preact/signals-react

INFO

@preact/signals-react is a peer dependency of @formsignals/form-react and needs to be installed as well.

WARNING

If you are using Babel or are running into issues with the installation of @preact/signals-react, you should check out their installation guide here.

If you want to use a schema validation library you can also install the corresponding package.

Creating your first form

This code snippet demonstrates a simple registration form with name, email, and password fields. The example utilizes Zod for validation.

TIP

Just like in this example it is advised to create your own input components, that accept signals as props.

tsx
import SignalInput from './SignalInput'
import FieldErrors from './FieldErrors'
import {useForm} from '@formsignals/form-react';
import {ZodAdapter} from "@formsignals/validation-adapter-zod";
import {z} from "zod";

export default function App() {
  const form = useForm({
    validatorAdapter: ZodAdapter,
    defaultValues: {
      username: '',
      email: '',
      password: '',
      confirmPassword: '',
    },
    onSubmit: (values) => {
      console.log('Form submitted with values', values)
    },
  })

  return (
    <form.FormProvider>
      <form
        onSubmit={(event) => {
          event.preventDefault()
          event.stopPropagation()
          form.handleSubmit()
        }}
      >
        <form.FieldProvider name="username" validator={z.string().min(3)}>
          {(field) => (
            <>
              <SignalInput
                type="text"
                placeholder="Username"
                name={field.name}
                value={field.data}
              />
              <FieldErrors/>
            </>
          )}
        </form.FieldProvider>
        <form.FieldProvider name="email" validator={z.string().email()}>
          {(field) => (
            <>
              <SignalInput
                type="email"
                placeholder="Email"
                name={field.name}
                value={field.data}
              />
              <FieldErrors/>
            </>
          )}
        </form.FieldProvider>
        <form.FieldProvider name="password" validator={z.string().min(8)}>
          {(field) => (
            <>
              <SignalInput
                type="password"
                placeholder="Password"
                name={field.name}
                value={field.data}
              />
              <FieldErrors/>
            </>
          )}
        </form.FieldProvider>
        <form.FieldProvider
          name="confirmPassword"
          validator={z
            .tuple([z.string(), z.string()])
            .refine(
              ([checkValue, originalValue]) => checkValue === originalValue,
              'Passwords do not match',
            )}
          validateMixin={['password'] as const}
        >
          {(field) => (
            <>
              <SignalInput
                type="password"
                placeholder="Confirm Password"
                name={field.name}
                value={field.data}
              />
              <FieldErrors/>
            </>
          )}
        </form.FieldProvider>
        <button type="submit">Submit</button>
      </form>
    </form.FormProvider>
  )
}
tsx
import type {Signal} from '@preact/signals-react'
import type {InputHTMLAttributes} from 'react'

interface SignalInputProps
  extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'> {
  value: Signal<string>
}

export default function SignalInput({value, ...props}: SignalInputProps) {
  return (
    <input
      {...props}
      value={value.value}
      onInput={(event) => {
        value.value = event.currentTarget.value
      }}
    />
  )
}
tsx
import {useFieldContext} from '@formsignals/form-react'

export default function FieldErrors() {
  const field = useFieldContext()
  return (
    <div>
      {field.errors.value.map((error, index) => (
        <div key={index}>{error}</div>
      ))}
    </div>
  )
}

Interactive Example