Skip to content

componentDidUpdate infinite loop (React error #185) when columnsBlock contains multiple empty columns #119

Description

@wesleybl

When a columnsBlock contains two or more columns with empty data (blocks: {}, blocks_layout: { items: [] }), the editor crashes with React error #185 (Maximum update depth exceeded).

Root cause:

In ColumnsBlockEdit.jsx, the componentDidUpdate lifecycle method iterates over all columns and calls onChangeBlock individually for each empty column found:

if (hasColumns(data.data)) {
  forEachColumn(data.data, ([colId, colData]) => {
    if (columnIsEmpty(colData)) {
      onChangeBlock(block, newData); // called once per empty column
    }
  });
}

The problem is that each onChangeBlock call is based on the same data snapshot from the beginning of the cycle. When there are multiple empty columns, the calls run in parallel without seeing each other's corrections:

1. componentDidUpdate → column A empty → onChangeBlock(fixes A)
                      → column B empty → onChangeBlock(fixes B) ← same data, doesn't see A fixed
2. componentDidUpdate → column A empty → onChangeBlock(fixes A) ← B overwrote A
                      → column B empty → onChangeBlock(fixes B)
3. ... ∞

Each onChangeBlock overwrites the previous one because they all start from the same stale snapshot. The state never converges, causing an infinite update loop.

With a single empty column this accidentally worked — it converged in 2 cycles. With two or more empty columns it never converges.

How it was triggered:

This was triggered by content imported via migration, where several columnsBlock entries were saved with multiple empty columns (blocks: {}, blocks_layout: { items: [] }). In normal editor usage, columns are always initialized with content, so this code path was never exercised with more than one empty column at a time.

Why the fix is simply removing the block:

The forEachColumn block in componentDidUpdate is redundant. The BlocksForm component already handles empty columns gracefully at render time:

properties={{
  ...metadata,
  ...(isEmpty(column) ? emptyBlocksForm() : column),
}}

When a column is empty, emptyBlocksForm() is passed and the block renders correctly without needing to persist anything. The componentDidUpdate block was attempting to fix at runtime what the render layer already handled — at a continuous performance cost for every update cycle, for every user, even those with no empty columns.

Fix:

Remove the forEachColumn block from componentDidUpdate in ColumnsBlockEdit.jsx:

// Remove this entire block
if (hasColumns(data.data)) {
  forEachColumn(data.data, ([colId, colData]) => {
    if (columnIsEmpty(colData)) {
      const newData = { ... };
      onChangeBlock(block, newData);
    }
  });
}

Impact of the fix:

  • Eliminates the infinite loop and React error #185
  • Zero performance cost — removes unnecessary iteration on every update cycle
  • No behavior change for normal editor usage
  • Empty columns from migrated content are already handled correctly by the render layer

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions