Skip to content

Mutating fields without forms #34

@ashtonjurgens

Description

@ashtonjurgens

The second approach would be what you already did but with a modified version of the ValidPath type that let's you specify a datatype so that e.g. only paths to string fields can be specified.

We could also start an issue to discuss whether our field hook and component should provide a connected version of our setInput method where you directly can pass over the new value without specifying the form and path.

Originally posted by @fabian-hiller in #32

The ability to mutate a field without a reference to the form would allow me to use less code, and a more convenient API when building complex input components.

I have some thoughts on how this should work

A form is a field

I'll explain by example: I have a "user form" for getting the user data I need.

const AddressSchema = v.object({
    street: v.string(),
    city: v.string(),
    state: v.string(),
    zip: v.string()
})

const UserSchema = v.object({
    firstName: v.string(),
    lastName: v.string(),
    address: AddressSchema
})
Image

Somewhere else, I need a form for just the address. So I create an "address form" component. The "user form" can now just use the address form, within it. This is an example of how a form can be a field, and something I keep encountering in my usage.

From another perspective, consider how a form is based on a valibot schema. In valibot, UserSchema is a schema, and AddressSchema is a schema. But on my user form, the UserSchema becomes the form, which is treated one way, and AddressSchema becomes a field, which is treated differently.

setInput

I would prefer an API like this

// No changes
const form = createForm({ schema: UserSchema })
const address = useField(form, { path: ["address"] })

// Current API still works
setInput(form, {
    path: ["firstName"],
    input: "John"
})

// Changes below
// Set input of field (without path)
setInput(address, {
    input: {
        street: "123 Main St",
        city: "Anytown",
        state: "CA",
        zip: "12345"
    }
})

// Set input of field (with path)
setInput(address, {
    path: ["street"],
    input: "456 Oak Ave"
})

useFieldArray (and FieldArray)

This primitive requires a path, so when I want to use it on an array schema I must wrap it in an object. This goes against my "a form is a field" ideal. Perhaps there are other areas where paths should not be required.

Advantages

The largest practical advantage for me, is that when I'm building and using components (#32), I only care about the field store. The form it is a part of and the path in that form are of no concern in the component (the component probably wouldn't be reusable if it were).

When using my component, I can just pass the field

{/* Current API */}
<AddressField of={form} path={["address"]}  />

{/* Preferred API */}
<Field of={form} path={["address"]}>
  {(field) => <AddressField field={field} />}
</Field>

This makes typing simple, and also works when the form was created with the AddressSchema.

{/* Just need to pass the form */}
<AddressField field={form} />

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestpriorityThis has priority

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions