Check out our talk at Remix Conf!

Dynamic form

In this example, we render a dynamic form with the fields coming from the backend.

type FieldType = 'string' | 'email' | 'int'
type Field = { name: string; type: FieldType }

const getFields = () => {
  // This would come from a database
  const fields: Field[] = [
    { name: 'firstName', type: 'string' },
    { name: 'email', type: 'email' },
    { name: 'age', type: 'int' },
  ]

  return fields
}

const typeSchemas = {
  string: z.string(),
  email: z.string().email(),
  int: z.number().int(),
}

const fieldSchema = (type: FieldType) => typeSchemas[type]

const fieldsSchema = (fields: Field[]) =>
  z.object(
    fields.reduce(
      (obj, field) => ({ ...obj, [field.name]: fieldSchema(field.type) }),
      {},
    ),
  )

const mutation = makeDomainFunction(fieldsSchema(getFields()))(
  async (values) => values,
)

export function loader(_args: LoaderArgs) {
  return { fields: getFields() }
}

export const action: ActionFunction = async ({ request }) =>
  formAction({ request, schema: fieldsSchema(getFields()), mutation })

export default () => {
  const { fields } = useLoaderData<typeof loader>()

  return <Form schema={fieldsSchema(fields)} />
}