Form flowYield

Form flow

Yield

Learn how the yield element is used in the flow.


Usage

The yield element is used to yield values when navigating between steps. When values are yielded the onYield callback of the Formity component will be called.

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

import { useCallback, useState } from "react";

import {
  Formity,
  type s,
  type Flow,
  type OnYield,
  type OnReturn,
  type ReturnOutput,
} from "@formity/react";

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

import { Form } from "./components/form";
import { Output } from "./components/output";

type Schema = {
  render: React.ReactNode;
  struct: [
    s.Form<{ name: string; surname: string; age: number }>,
    s.Yield<{
      next: [{ name: string; surname: string; age: number }];
      back: [];
    }>,
    s.Form<{ softwareDeveloper: string }>,
    s.Yield<{
      next: [{ softwareDeveloper: string }];
      back: [];
    }>,
    s.Return<{
      name: string;
      surname: string;
      age: number;
      softwareDeveloper: string;
    }>,
  ];
  inputs: Record<never, never>;
  params: Record<never, never>;
};

const flow: Flow<Schema> = [
  {
    form: {
      fields: () => ({
        name: ["", []],
        surname: ["", []],
        age: [20, []],
      }),
      render: ({ fields, onBack, onNext }) => (
        <Form
          key="yourself"
          defaultValues={fields}
          resolver={zodResolver(
            z.object({
              name: z.string().nonempty("Required"),
              surname: z.string().nonempty("Required"),
              age: z.number().min(18, "Min. 18").max(99, "Max. 99"),
            }),
          )}
          heading="Tell us about yourself"
          content={[
            {
              type: "columns",
              columns: [
                {
                  type: "input",
                  name: "name",
                  label: "Name",
                  placeholder: "Your name",
                },
                {
                  type: "input",
                  name: "surname",
                  label: "Surname",
                  placeholder: "Your surname",
                },
              ],
            },
            {
              type: "number",
              name: "age",
              label: "Age",
              placeholder: "Your age",
            },
          ]}
          buttons={{
            back: null,
            next: "Next",
          }}
          onBack={onBack}
          onNext={onNext}
        />
      ),
    },
  },
  {
    yield: {
      next: ({ name, surname, age }) => [{ name, surname, age }],
      back: () => [],
    },
  },
  {
    form: {
      fields: () => ({
        softwareDeveloper: ["", []],
      }),
      render: ({ fields, onBack, onNext }) => (
        <Form
          key="softwareDeveloper"
          defaultValues={fields}
          resolver={zodResolver(
            z.object({
              softwareDeveloper: z.string().nonempty("Required"),
            }),
          )}
          heading="Are you a software developer?"
          content={[
            {
              type: "select",
              name: "softwareDeveloper",
              label: "Software Developer",
              placeholder: "Select an option",
              options: [
                { value: "yes", label: "Yes" },
                { value: "no", label: "No" },
              ],
            },
          ]}
          buttons={{
            back: "Back",
            next: "Next",
          }}
          onBack={onBack}
          onNext={onNext}
        />
      ),
    },
  },
  {
    yield: {
      next: ({ softwareDeveloper }) => [{ softwareDeveloper }],
      back: () => [],
    },
  },
  {
    return: ({ name, surname, age, softwareDeveloper }) => ({
      name,
      surname,
      age,
      softwareDeveloper,
    }),
  },
];

export default function App() {
  const [output, setOutput] = useState<ReturnOutput<Schema> | null>(null);

  const onYield = useCallback<OnYield<Schema>>((output) => {
    console.log(output);
  }, []);

  const onReturn = useCallback<OnReturn<Schema>>((output) => {
    setOutput(output);
  }, []);

  if (output) {
    return <Output output={output} onStart={() => setOutput(null)} />;
  }

  return <Formity<Schema> flow={flow} onYield={onYield} onReturn={onReturn} />;
}

We need to use the s.Yield type and define the types of the values to be yielded when navigating to the next and previous steps.

type Schema = {
  // ...
  struct: [
    // ...
    s.Yield<{
      next: [{ name: string; surname: string; age: number }];
      back: [];
    }>,
    // ...
    s.Yield<{
      next: [{ softwareDeveloper: string }];
      back: [];
    }>,
    // ...
  ];
  // ...
};

Then, in the flow we need to create an object with the following structure.

const flow: Flow<Schema> = [
  // ...
  {
    yield: {
      next: ({ name, surname, age }) => [{ name, surname, age }],
      back: () => [],
    },
  },
  // ...
  {
    yield: {
      next: ({ softwareDeveloper }) => [{ softwareDeveloper }],
      back: () => [],
    },
  },
  // ...
];

The next function takes the input values and returns an array with the values to be yielded. Each array element will trigger the onYield callback function.

The back function works the same way with the difference that the values will be yielded when navigating to the previous steps.