Basic Usage
Every form in HouseForm starts with a <Form>
component:
import { Form } from "houseform";
const App = () => (
<Form onSubmit={() => {}}>
{({ submit }) => <button onClick={submit}>Submit</button>}
</Form>
);
Within Form
, there's a required onSubmit
prop. This property should be a function that you want ran when the submit
function is ran.
The child of a Form
should be a function that returns a JSX element. This can be anything - a div
, Fragment
, or anything in between.
Basic <Field>
usage
Once you have a <Form>
established, you'll want to add a <Field>
component.
import { Form, Field } from "houseform";
const App = () => (
<Form onSubmit={() => {}}>
{({ submit }) => (
<div>
<Field name="username" initialValue={""}>
{({ value, setValue, onBlur }) => (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
/>
)}
</Field>
<button onClick={submit}>Submit</button>
</div>
)}
</Form>
);
This is the most basic a form field can get within HouseForm. This field has:
name
- A string that can be used to lookup the field and handle the result value during form submission.intialValue
.- A child function.
Much like the parent <Form>
component, the function of Field
will render the contained input
element without adding any UI elements. It is your responsibility to pass the appropriate fields to a given input
. In this case, we're passing the three required fields to make a text input work:
value
- The current value of the field.setValue
- A function used to update the current field value.onBlur
- A function used to track internal logic pertaining to user input.
Getting a form's value on submission
Now that we have a field, we can update our <Form>
's onSubmit
function to show an alert
when the form is submitted:
import { Form, Field } from "houseform";
const App = () => (
<Form onSubmit={(values) => alert(values)}>
{({ submit }) => (
<div>
<Field name="username" initialValue={""}>
{({ value, setValue, onBlur }) => (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
/>
)}
</Field>
<button onClick={submit}>Submit</button>
</div>
)}
</Form>
);
This values
object will represent the fields within the form. For example, when submitting this form, you'll see:
{
"username": "User input here"
}
If you had a second <Field>
component rendered with name="email"
you would see:
{
"username": "User input here",
"email": "User input here"
}
Adding field validation
Now that we have a field, a form, and can receive values from the form's submission - let's add some validation!
Validation in HouseForm is done on a per-field basis:
import { Field } from "houseform";
import { z } from "zod";
// ...
const App = () => (
<Field
name="username"
initialValue={""}
onChangeValidate={z
.string()
.min(3, "Your username must have at least three characters")}
>
{({ value, setValue, onBlur, errors }) => (
<>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
/>
{errors.map((error) => (
<p key={error}>{error}</p>
))}
</>
)}
</Field>
);
This field, for example, will validate the user's input whenever they type a value. If said input is less than three characters, Zod will run its validation and present the error to the user via the errors.map
logic.
Alternative forms of validation
Validation as the user is typing is not always the most ideal form of validation, however. Luckily, HouseForm supports two other methods of validation:
- On blur via
onBlurValidate
<Field name="username" initialValue={""} onBlurValidate={z.string().min(3, "Your username must have at least three characters")}>
{({value, setValue, onBlur, errors}) => (
<input value={value} onChange={e => setValue(e.target.value)} onBlur={onBlur}/>
{errors.map(error => <p key={error}>{error}</p>)}
)}
</Field>
This will only run validation when the user has blurred the field.
- On submit via
onSubmitValidate
<Field name="username" initialValue={""} onSubmitValidate={z.string().min(3, "Your username must have at least three characters")}>
{({value, setValue, onBlur, errors}) => (
<input value={value} onChange={e => setValue(e.target.value)} onBlur={onBlur}/>
{errors.map(error => <p key={error}>{error}</p>)}
)}
</Field>
This will only run validation when the user submits the form.
Validate only once touched
While HouseForm does not provide an explicit method to validate only once touched, you may reproduce this behavior using the isTouched
property passed to each field's child function:
<Field name="username" initialValue={""} onChangeValidate={z.string().min(3, "Your username must have at least three characters")}>
{({value, setValue, onBlur, errors, isTouched}) => (
<input value={value} onChange={e => setValue(e.target.value)} onBlur={onBlur}/>
{isTouched && errors.map(error => <p key={error}>{error}</p>)}
)}
</Field>
Async function validation
While it's recommended to use Zod to do input validation in HouseForm, sometimes applications have more custom requirements. This is why we provide a method of using an asynchronous function to validate a field. To do this, pass a function to any of the onXYZValidate
<Field>
properties:
<Field name="email" initialValue={""} onChangeValidate={async value => {return await isEmailUnique(value)}}>
{({value, setValue, onBlur, errors, isTouched}) => (
<input value={value} onChange={e => setValue(e.target.value)} onBlur={onBlur}/>
{isTouched && errors.map(error => <p key={error}>{error}</p>)}
)}
</Field>
Here, we expect the async function to do one of two things:
- Resolve
true
- Reject with a string explaining why it was rejected
// This is simulating a check against a database
function isEmailUnique(val: string) {
return new Promise<boolean>((resolve, reject) => {
setTimeout(() => {
const isUnique = !val.startsWith("crutchcorn");
if (isUnique) {
resolve(true);
} else {
reject("That email is already taken");
}
}, 20);
});
}
If resolved, it will validate; Otherwise it will pass the rejection explanation to the errors
property of the <Field>
.