houseform
Version:
Simple to use React forms, where your validation and UI code live together in harmony.
181 lines (164 loc) • 5.75 kB
Markdown
where you'll want to link two fields together; when one is validated another is as well. A prime example of this is when establishing `password` and `confirmpassword` fields, where you want to update the form's errors when `password` is edited.
The first step to do this is to figure out how to cross-reference one field's value in another field's validation.
This can be done using the second argument of any `Validate` function and utilizing the`getFieldValue` function:
```tsx
import { Field } from "houseform";
const App = () => (
<Field
name="confirmpassword"
listenTo={["password"]}
onChangeValidate={(val, form) => {
if (val === form.getFieldValue("password")!.value) {
return Promise.resolve(true);
} else {
return Promise.reject("Passwords must match");
}
}}
>
{/* ... */}
</Field>
);
```
While this works, it introduces some edgecase bugs. Imagine the following userflow:
- User updates confirm password field.
- User updates the non-confirm password field.
In this example, the form will still have errors present, as the confirm password field validation has not been re-ran to mark as accepted.
To solve this, we need to make sure that the confirm password validation is re-ran when the password field is updated. To do this, you just need to add a `listenTo` field to one of the two form fields.
```tsx
import { Field } from "houseform";
const App = () => (
<>
<Field
name="password"
onChangeValidate={z.string().min(8, "Must be at least 8 characters long")}
>
{({ value, setValue, onBlur, errors }) => {
return (
<div>
<input
value={value}
onBlur={onBlur}
onChange={(e) => setValue(e.target.value)}
placeholder={"Password"}
type="password"
/>
{errors.map((error) => (
<p key={error}>{error}</p>
))}
</div>
);
}}
</Field>
<Field
name="confirmpassword"
listenTo={["password"]}
onChangeValidate={(val, form) => {
if (val === form.getFieldValue("password")!.value) {
return Promise.resolve(true);
} else {
return Promise.reject("Passwords must match");
}
}}
>
{({ value, setValue, onBlur, errors, isTouched }) => {
return (
<div>
<input
value={value}
onBlur={onBlur}
onChange={(e) => setValue(e.target.value)}
placeholder={"Password Confirmation"}
type="password"
/>
{isTouched && errors.map((error) => <p key={error}>{error}</p>)}
</div>
);
}}
</Field>
</>
);
```
This `listenTo` field expects the name of another field to be passed to it as an array. When the listened to field has events ran on it (say, `onBlur`), it will run the respective validation for the field doing the listening as well.
## Conditional Fields
Similarly, you may have instances where you want to show or hide a field based on the value of another field. This can be done using a form's `value` property:
```tsx
import { Form, Field } from "houseform";
function App() {
return (
<Form
onSubmit={(values) => {
alert("Form was submitted with: " + JSON.stringify(values));
}}
>
{({ isValid, submit, value: formValue }) => {
// On first render, `value` is an empty object, since the `email` field is not yet initialized.
const isGmail = formValue.email?.endsWith("@gmail.com");
return (
<form
onSubmit={(e) => {
e.preventDefault();
submit();
}}
>
<Field
name="email"
initialValue={"test@gmail.com"}
onBlurValidate={z.string().email("This must be an email")}
>
{({ value, setValue, onBlur, errors }) => {
return (
<div>
<input
value={value}
onBlur={onBlur}
onChange={(e) => setValue(e.target.value)}
placeholder={"Email"}
/>
{errors.map((error) => (
<p key={error}>{error}</p>
))}
</div>
);
}}
</Field>
{isGmail && (
<p style={{ color: "red" }}>
We don't currently support gmail as an email host
</p>
)}
{!isGmail && (
<Field
name="password"
onChangeValidate={z
.string()
.min(8, "Must be at least 8 characters long")}
>
{({ value, setValue, onBlur, errors }) => {
return (
<div>
<input
value={value}
onBlur={onBlur}
onChange={(e) => setValue(e.target.value)}
placeholder={"Password"}
type="password"
/>
{errors.map((error) => (
<p key={error}>{error}</p>
))}
</div>
);
}}
</Field>
)}
<button disabled={!isValid || isGmail} type="submit">
Submit
</button>
</form>
);
}}
</Form>
);
}
```
There are times