Basic concepts
Form state
Learn how to access the form state for future use.
Form state
When rendering each form, we can access the getState
function, and it is used to access the current state of the multi-step form:
import type { Schema, Form, Return } from "@formity/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormView, FormLayout, TextField, Next, Back } from "./components";
import { Controller } from "./controller";
export type Values = [
Form<{ name: string }>,
Form<{ surname: string }>,
Return<{ name: string; surname: string }>,
];
export const schema: Schema<Values> = [
{
form: {
values: () => ({
name: ["", []],
}),
render: ({ values, onNext, onBack, getState, setState }) => (
<Controller
step="name"
onNext={onNext}
onBack={onBack}
getState={getState}
setState={setState}
>
<FormView
defaultValues={values}
resolver={zodResolver(
z.object({
name: z.string(),
}),
)}
>
<FormLayout
heading="What is your name?"
description="We would like to know what is your name"
fields={[<TextField key="name" name="name" label="Name" />]}
button={<Next>Next</Next>}
/>
</FormView>
</Controller>
),
},
},
{
form: {
values: () => ({
surname: ["", []],
}),
render: ({ values, onNext, onBack, getState, setState }) => (
<Controller
step="surname"
onNext={onNext}
onBack={onBack}
getState={getState}
setState={setState}
>
<FormView
defaultValues={values}
resolver={zodResolver(
z.object({
surname: z.string(),
}),
)}
>
<FormLayout
heading="What is your surname?"
description="We would like to know what is your surname"
fields={[
<TextField key="surname" name="surname" label="Surname" />,
]}
button={<Next>Next</Next>}
back={<Back />}
/>
</FormView>
</Controller>
),
},
},
{
return: ({ name, surname }) => ({
name,
surname,
}),
},
];
This function takes the current form's values and returns a State
object that represents the current state. This object can be stored, for example, in local storage, allowing the form to resume from the last step at a later time:
import type { ReactElement } from "react";
import type { UseFormProps } from "react-hook-form";
import type { State } from "@formity/react";
import { useEffect } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { useController } from "@/controller";
interface FormViewProps {
defaultValues: UseFormProps["defaultValues"];
resolver: UseFormProps["resolver"];
children: ReactElement;
}
export default function FormView({
defaultValues,
resolver,
children,
}: FormViewProps) {
const form = useForm({ defaultValues, resolver });
const { onNext, getState } = useController();
useEffect(() => {
const { unsubscribe } = form.watch((values) => {
const state = getState(values);
saveState(state);
});
const state = getState(form.getValues());
saveState(state);
return () => unsubscribe();
}, [form, getState]);
return (
<form onSubmit={form.handleSubmit(onNext)} className="h-full">
<FormProvider {...form}>{children}</FormProvider>
</form>
);
}
function saveState(state: State) {
localStorage.setItem("state", JSON.stringify(state));
}
The initialState
prop of the Formity
component allows us to start the form with a specific state:
import { useCallback, useState } from "react";
import { Formity, OnReturn, ReturnValues, State } from "@formity/react";
import { Data } from "./components";
import { schema, Values } from "./schema";
const initialState = getInitialState();
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}
initialState={initialState}
/>
);
}
function getInitialState(): State | undefined {
const state = localStorage.getItem("state");
if (state) return JSON.parse(state);
return undefined;
}
Additionally, the setState
function can be used to define the state of the multi-step form. Like the getState
function, it is accessible while rendering each form.