Managing Array and Block Rows with Ease

Easily move your rows around with custom components
Managing form state within arrays and blocks has been a bit cumbersome. We just shipped new ways to make it easier. Payload is built with extensibility in mind. In this post we will show how easy it is to add, replace and remove rows within array and block fields.

In this example collection we have two fields, an array field and a custom ui field.

const ExampleCollection: CollectionConfig = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: CustomArrayManager,
},
},
},
],
};

The ui field’s custom component will allow us to perform actions on the array field. Here is a snippet of what this could look like:

import * as React from "react";
import { useForm } from "payload/components/forms";
export const CustomArrayManager: React.FC = () => {
const { addFieldRow, replaceFieldRow, removeFieldRow } = useForm();
return (
<div style={{ display: "flex", gap: "10px" }}>
<button
onClick={() => {
addFieldRow({
path: "arrayField",
rowIndex: 0,
data: {
textField: "text",
},
});
}}
type="button"
>
Add Row
</button>
<button
type="button"
onClick={() => {
replaceFieldRow({
path: "arrayField",
rowIndex: 0,
data: {
textField: "updated text",
},
});
}}
>
Replace Row
</button>
<button
type="button"
onClick={() => {
removeFieldRow({
path: "arrayField",
rowIndex: 0,
});
}}
>
Remove Row
</button>
</div>
);
};

To interact with the form data, we now expose a couple helper functions through Payloads useForm hook.

Managing Array rows

addFieldRow

You can use this when you want to programmatically add a new row to an array field:

const { addFieldRow } = useForm();
return (
<button
onClick={() => {
addFieldRow({
path: "arrayField",
rowIndex: 0,
data: {
textField: "text",
},
});
}}
type="button"
>
Add Row
</button>
)

Arguments:

  • path: the path of the field that you want to target. If this was a nested field, you would need to pass the whole path, i.e. outerArrayField.0.innerArrayField
  • rowIndex: (optional) the row index used to insert the data at
  • data: the new row data
replaceFieldRow

You can use this when you want to programmatically replace a row within an array field:

const { replaceFieldRow } = useForm();
return (
<button
onClick={() => {
replaceFieldRow({
path: "arrayField",
rowIndex: 0,
data: {
textField: "updated text",
},
});
}}
type="button"
>
Replace Row
</button>
)

Arguments:

  • path: the path of the field that you want to target. If this was a nested field, you would need to pass the whole path, i.e. outerArrayField.0.innerArrayField
  • rowIndex: the index of the row that you want to replace
  • data: the replacement row data
removeFieldRow

To be used when you want to programmatically remove a row from an array field:

const { removeFieldRow } = useForm();
return (
<button
onClick={() => {
removeFieldRow({
path: "arrayField",
rowIndex: 0,
});
}}
type="button"
>
Remove Row
</button>
)

Arguments:

  • path: the path of the field that you want to target. If this was a nested field, you would need to pass the whole path, i.e. outerArrayField.0.innerArrayField
  • rowIndex: the index of the row that you want to remove

Managing Block rows

The implementation for blocks is identical to arrays, just be sure to pass blockType in the data object. Here is another simple collection example we have a blocks field and our custom ui field:

const ExampleCollection: CollectionConfig = {
slug: "example-collection",
fields: [
{
name: "blocksField",
type: "blocks",
blocks: [
{
slug: "textBlock",
fields: [
{
name: "textField",
type: "text",
},
]
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: CustomArrayManager,
},
},
},
],
};
addFieldRow

You can use this just like in the array example, just pass the blockType along with the row data:

const { addFieldRow } = useForm();
return (
<button
onClick={() => {
addFieldRow({
path: "blocks",
rowIndex: 0,
data: {
blockType: "textBlock",
textField: "block text",
},
});
}}
type="button"
>
Add Block
</button>
)
replaceFieldRow

You can use this just like in the array example, just pass the blockType along with the row data:

const { replaceFieldRow } = useForm();
return (
<button
onClick={() => {
replaceFieldRow({
path: "blocks",
rowIndex: 0,
data: {
blockType: "textBlock",
textField: "updated block text",
},
});
}}
type="button"
>
Replace Block
</button>
)
removeFieldRow

This is the exact same for array and block fields.

Recap

This is a bit of a technical update, but I think this feature warrants it because of the potential power it holds. With this you can easily manage blocks and arrays to fit your custom needs. You could create a custom component where the user selects a block type from a dropdown, writes a prompt and then you programmatically insert generated data that fits the block type into the form.

There are a lot of possibilities when extending Payload, the goal is to make it easier.

Learn more

Custom CSS
How to Customize the Look and Feel of Payload with CSS

The Custom CSS feature in Payload extends beyond being a simple customization tool–it’s a pathway to seamlessly integrate your CMS with your brand.

Payload Form Builder Plugin
Create custom forms with the official Form Builder Plugin

Tired of dealing with microservices hell? Implement and create forms with ease using Payload’s free form builder plugin.

Open-source repo with zero GitHub issues
On GitHub Issues and Engineering Efficiency

Here's how Payload goes about its open-source GitHub issues—how we keep them as accurate as possible, how we avoid distractions, and more.

Use a multi-tenant app to serve the same application on several different domains
How To Build A Multi-Tenant App With Payload

Cut costs, save time, and ship faster by sharing infrastructure when you setup Payload as a multi-tenant application.