Form schema

Loop

Learn how the Loop element is used in the schema.


Usage

The Loop element is used to define a loop.

To understand how it is used let's look at this example:

import type { Schema, Form, Loop, Return, Variables } from "@formity/react";

import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

import { FormView, FormLayout, Select, Next, Back } from "./components";

import { Controller } from "./controller";

export type Values = [
  Variables<{ languages: { value: string; question: string }[] }>,
  Variables<{
    i: number;
    languagesRatings: { name: string; rating: string }[];
  }>,
  Loop<
    [
      Variables<{ language: { value: string; question: string } }>,
      Form<{ rating: string }>,
      Variables<{
        i: number;
        languagesRatings: { name: string; rating: string }[];
      }>,
    ]
  >,
  Return<{ languagesRatings: { name: string; rating: string }[] }>,
];

export const schema: Schema<Values> = [
  {
    variables: () => ({
      languages: [
        {
          value: "javascript",
          question: "What rating would you give to the JavaScript language?",
        },
        {
          value: "python",
          question: "What rating would you give to the Python language?",
        },
        {
          value: "go",
          question: "What rating would you give to the Go language?",
        },
      ],
    }),
  },
  {
    variables: () => ({
      i: 0,
      languagesRatings: [],
    }),
  },
  {
    loop: {
      while: ({ i, languages }) => i < languages.length,
      do: [
        {
          variables: ({ i, languages }) => ({
            language: languages[i],
          }),
        },
        {
          form: {
            values: ({ language }) => ({
              rating: ["love-it", [language.value]],
            }),
            render: ({
              inputs,
              values,
              onNext,
              onBack,
              getState,
              setState,
            }) => (
              <Controller
                step={`rating-${inputs.language.value}`}
                onNext={onNext}
                onBack={onBack}
                getState={getState}
                setState={setState}
              >
                <FormView
                  defaultValues={values}
                  resolver={zodResolver(
                    z.object({
                      rating: z.string(),
                    }),
                  )}
                >
                  <FormLayout
                    heading={inputs.language.question}
                    description="We would like to know how much you like it"
                    fields={[
                      <Select
                        key="rating"
                        name="rating"
                        label="Rating"
                        options={[
                          { value: "love-it", label: "Love it" },
                          { value: "like-it-a-lot", label: "Like it a lot" },
                          { value: "it-is-okay", label: "It is okay" },
                        ]}
                        direction="y"
                      />,
                    ]}
                    button={<Next>Next</Next>}
                    back={inputs.i > 0 ? <Back /> : undefined}
                  />
                </FormView>
              </Controller>
            ),
          },
        },
        {
          variables: ({ i, languagesRatings, language, rating }) => ({
            i: i + 1,
            languagesRatings: [
              ...languagesRatings,
              { name: language.value, rating },
            ],
          }),
        },
      ],
    },
  },
  {
    return: ({ languagesRatings }) => ({
      languagesRatings,
    }),
  },
];

We need to use the Loop type with the corresponding types:

export type Values = [
  // ...
  Loop<
    [
      Variables<{ language: { value: string; question: string } }>,
      Form<{ rating: string }>,
      Variables<{
        i: number;
        languagesRatings: { name: string; rating: string }[];
      }>,
    ]
  >,
  // ...
];

Then, in the schema we need to create an object with the following structure:

export const schema: Schema<Values> = [
  // ...
  {
    loop: {
      while: ({ i, languages }) => i < languages.length,
      do: [
        // ...
      ],
    },
  },
  // ...
];

The while property is a function that takes the values generated in previous steps and returns a boolean value. When the value is true, the elements in the do property are used.

It is important to note that the step prop is dynamically generated to ensure a unique value for each iteration. Additionally, a value is passed in the rating array to prevent the value from persisting across iterations:

export const schema: Schema<Values> = [
  // ...
  {
    loop: {
      while: ({ i, languages }) => i < languages.length,
      do: [
        // ...
        {
          form: {
            values: ({ language }) => ({
              rating: ["love-it", [language.value]],
            }),
            render: ({
              inputs,
              values,
              onNext,
              onBack,
              getState,
              setState,
            }) => (
              <Controller
                step={`rating-${inputs.language.value}`}
                onNext={onNext}
                onBack={onBack}
                getState={getState}
                setState={setState}
              >
                <FormView
                  defaultValues={values}
                  resolver={zodResolver(
                    z.object({
                      rating: z.string(),
                    }),
                  )}
                >
                  <FormLayout
                    heading={inputs.language.question}
                    description="We would like to know how much you like it"
                    fields={[
                      <Select
                        key="rating"
                        name="rating"
                        label="Rating"
                        options={[
                          { value: "love-it", label: "Love it" },
                          { value: "like-it-a-lot", label: "Like it a lot" },
                          { value: "it-is-okay", label: "It is okay" },
                        ]}
                        direction="y"
                      />,
                    ]}
                    button={<Next>Next</Next>}
                    back={inputs.i > 0 ? <Back /> : undefined}
                  />
                </FormView>
              </Controller>
            ),
          },
        },
        // ...
      ],
    },
  },
  // ...
];