Skip to main content

Using blocks in forms

This guide describes how to use a block in a Final Form. We use a product form with a DamImageBlock as an example. The product has the following schema in GraphQL:

type Product {
id: ID!
title: String!
image: DamImageBlockData!
}

The following steps are necessary to add the image field to the form:

  1. Create a Final Form component for the block
  2. Create the block state when initializing the form
  3. Create the block output when submitting the form
info

The block transitions between the various states when being used in a form. The states and transitions can be viewed here.

Create a Final Form component for the block

The block's AdminComponent API (state, updateState) must be converted to the Field API (input.value, input.onChange). Use the createFinalFormBlock helper for this:

import { createFinalFormBlock } from "@comet/blocks-admin";

const FinalFormDamImageBlock = createFinalFormBlock(DamImageBlock);

Use the newly created component in the corresponding Field via the component prop:

import isEqual from "lodash.isequal";

<Field
name="image"
isEqual={isEqual}
component={FinalFormDamImageBlock}
/>;
note

A deep equality check using isEqual is necessary because the block state is an object.

Create the block state when initializing the form

The block state needs to be created when initializing the form. If a product exists (i.e., in edit mode), transform the block input to the block state. When creating a new product, use the block's default values:

import { BlockState } from "@comet/blocks-admin";
import isEqual from "lodash.isequal";

type FormValues = Omit<GQLProductFormFragment, "image"> & {
image: BlockState<typeof DamImageBlock>;
};

const { data } = useQuery(productFormQuery, id ? { variables: { id } } : { skip: true });

const initialValues = useMemo<Partial<FormValues>>(
() =>
data?.product
? {
...filterByFragment<GQLProductFormFragment>(productFormFragment, data.product),
image: DamImageBlock.input2State(data.product.image),
}
: {
inStock: false,
image: DamImageBlock.defaultValues(),
},
[data],
);

<FinalForm
initialValues={initialValues}
initialValuesEqual={isEqual}
/>;
note

A deep equality check using initialValuesEqual is necessary when using blocks in a form.

Create the block output when submitting the form

Transform the block state to the block output before submitting the form:

const handleSubmit = async (values: FormValues) => {
const input = {
...values,
image: DamImageBlock.state2Output(values.image),
};

/* Create or update the product... */
};