Advanced concepts
Conditional fields
Learn how to add fields that appear when a condition is met.
Conditional fields
We can create a field that appear when a condition is met. To do it, we can create a file named conditional-field.tsx
in the components
folder with the following code.
// components/conditional-field.tsx
import type { ReactNode } from "react";
import { useFormContext } from "react-hook-form";
interface ConditionalFieldProps<T extends object> {
condition: (values: T) => boolean;
values: string[];
children: ReactNode;
}
export function ConditionalField<T extends object>({
condition,
values,
children,
}: ConditionalFieldProps<T>) {
const { watch } = useFormContext();
const variables = watch(values).reduce(
(acc, value, index) => ({ ...acc, [values[index]]: value }),
{},
);
if (condition(variables)) {
return children;
}
return null;
}
Then, we can update the schema with the code shown below.
// schema.tsx
import type { Schema, Form, Return, Variables } from "@formity/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
FormStep,
FormStepContent,
FormStepHeading,
FormStepInputs,
} from "./components/form-step";
import { Select } from "./components/input/select";
import { TextInput } from "./components/input/text-input";
import { NextButton } from "./components/buttons/next-button";
import { ConditionalField } from "./components/conditional-field";
import { MultiStep } from "./multi-step";
export type Values = [
Form<{ working: string; company: string }>,
Variables<{ company: string | null }>,
Return<{ working: string; company: string | null }>,
];
export const schema: Schema<Values> = [
{
form: {
values: () => ({
working: ["no", []],
company: ["", []],
}),
render: ({ values, onNext, onBack }) => (
<MultiStep onNext={onNext} onBack={onBack}>
<FormStep
key="yourself"
defaultValues={values}
resolver={zodResolver(
z
.object({
working: z.string(),
company: z.string(),
})
.superRefine((data, ctx) => {
if (data.working === "yes") {
if (data.company === "") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Required",
path: ["company"],
});
}
}
}),
)}
>
<FormStepContent>
<FormStepHeading>Tell us about yourself</FormStepHeading>
<FormStepInputs>
<Select
name="working"
label="Are you working?"
options={[
{ value: "yes", label: "Yes" },
{ value: "no", label: "No" },
]}
/>
<ConditionalField<{ working: string }>
condition={(values) => values.working === "yes"}
values={["working"]}
>
<TextInput
name="company"
label="At what company?"
placeholder="Company name"
/>
</ConditionalField>
</FormStepInputs>
<NextButton>Submit</NextButton>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
variables: ({ working, company }) => ({
company: working === "yes" ? company : null,
}),
},
{
return: ({ working, company }) => ({
working,
company: company,
}),
},
];
We need to apply validation rules to the conditional field only when its condition is met. Additionally, we should set a default value for the field when it’s hidden, using a variable.