houseform
Version:
Simple to use React forms, where your validation and UI code live together in harmony.
228 lines (180 loc) • 7.11 kB
Markdown
---
head:
- - meta
- property: og:title
content: Basic HouseForm Usage
- - meta
- property: og:description
content: Learn how to use HouseForm; a React form library that colocates your forms' validation logic and UI into one place.
---
# Basic Usage
Every form in HouseForm starts with a `<Form>` component:
```tsx
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.
```tsx
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:
```tsx {2}
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:
```json
{
"username": "User input here"
}
```
If you had a second `<Field>` component rendered with `name="email"` you would see:
```json
{
"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:
```tsx
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](https://github.com/colinhacks/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:
1. On blur via `onBlurValidate`
```jsx
<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.
2. On submit via `onSubmitValidate`
```jsx
<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:
```jsx
<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:
```jsx
<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:
1. Resolve `true`
2. Reject with a string explaining why it was rejected
```typescript
// 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>`.