Advanced concepts
Conditional fields
Learn how to add fields that appear when a condition is met.
First steps
We can create fields that appear when a condition is met. To learn how to do it, clone the following Github repository so that you don't need to start from scratch.
git clone https://github.com/martiserra99/formity-react-docs
Make sure you run the following command to install all the dependencies:
npm install
Create conditional field
To create a conditional field, we will create a conditional-field.tsx
file with the following component:
// 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 default 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;
}
This component receives a condition, the list of values of the form that we want to work with, and the field that we want to render conditionally.
Once the component has been created, we can create a schema.tsx
file and use the component the following way:
// schema.tsx
import type { Schema, Form, Return, Variables } from "@formity/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormView, FormLayout, TextField, YesNo, Next } from "./components";
import { Controller } from "./controller";
import ConditionalField from "./conditional-field";
export type Values = [
Form<{ working: boolean; company: string }>,
Variables<{ company: string | null }>,
Return<{ working: boolean; company: string | null }>,
];
export const schema: Schema<Values> = [
{
form: {
values: () => ({
working: [true, []],
company: ["", []],
}),
render: ({ values, onNext, onBack, getState, setState }) => (
<Controller
step="working"
onNext={onNext}
onBack={onBack}
getState={getState}
setState={setState}
>
<FormView
defaultValues={values}
resolver={zodResolver(
z
.object({
working: z.boolean(),
company: z.string(),
})
.superRefine((data, ctx) => {
if (data.working) {
if (data.company === "") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Required",
path: ["company"],
});
}
}
}),
)}
>
<FormLayout
heading="Tell us about yourself"
description="We would want to know a little bit more about you"
fields={[
<YesNo key="working" name="working" label="Working" />,
<ConditionalField<{ working: boolean }>
key="company"
condition={({ working }) => working}
values={["working"]}
>
<TextField key="company" name="company" label="Company" />
</ConditionalField>,
]}
button={<Next>Next</Next>}
/>
</FormView>
</Controller>
),
},
},
{
variables: ({ working, company }) => ({
company: working ? company : null,
}),
},
{
return: ({ working, company }) => ({
working,
company,
}),
},
];
Apart from using the component to render the field conditionally, we also do some other things. We have the condition in the validation rules, and we also create a variable with a value that depends on the condition.
We now have to update the App.tsx
file so that it contains the following code:
// App.tsx
import { useCallback, useState } from "react";
import { Formity, OnReturn, ReturnValues } from "@formity/react";
import { Data } from "./components";
import { schema, Values } from "./schema";
export default function App() {
const [values, setValues] = useState<ReturnValues<Values> | null>(null);
const onReturn = useCallback<OnReturn<Values>>((values) => {
setValues(values);
}, []);
if (values) {
return <Data data={values} onStart={() => setValues(null)} />;
}
return <Formity<Values> schema={schema} onReturn={onReturn} />;
}