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.