ianstormtaylor/slate · critical · Error

Could not completely normalize the editor after ${maxIterati

Error message

Could not completely normalize the editor after ${maxIterations} iterations! This is usually due to incorrect normalization logic that leaves a node in an invalid state.

What it means

Slate normalizes the editor after every change by repeatedly running normalizeNode over 'dirty' paths until the document is valid. shouldNormalize caps this loop at initialDirtyPathsLength * 42 iterations as a safety hack; if normalization keeps marking nodes dirty (e.g. a normalizeNode override that never converges, always returns true for shouldNormalize-like hooks, or a normalize function that re-dirties the same path), the cap is hit and this error throws.

Source

Thrown at packages/slate/src/core/should-normalize.ts:11

import { WithEditorFirstArg } from '../utils/types'
import { Editor } from '../interfaces/editor'

export const shouldNormalize: WithEditorFirstArg<Editor['shouldNormalize']> = (
  editor,
  { iteration, initialDirtyPathsLength }
) => {
  const maxIterations = initialDirtyPathsLength * 42 // HACK: better way?

  if (iteration > maxIterations) {
    throw new Error(
      `Could not completely normalize the editor after ${maxIterations} iterations! This is usually due to incorrect normalization logic that leaves a node in an invalid state.`
    )
  }

  return true
}

View on GitHub (pinned to 72a37c701e)

Solutions

  1. Audit every custom normalizeNode override: make each fix idempotent — only act when the invalid condition is present, and always fall through to the previous normalizeNode for non-matching nodes
  2. Ensure normalize fixes don't call transforms that re-dirty the same path every pass (e.g. removing and re-adding the same property)
  3. Log the dirty paths per iteration (wrap Editor.normalize or shouldNormalize) to find the ping-pong pair of normalizers
  4. Reduce the problem: disable custom normalizers one by one until the loop stops to identify the culprit

Example fix

// before
const { normalizeNode } = editor
editor.normalizeNode = entry => {
  const [node] = entry
  editor.normalizeNode(entry) // re-normalizes same entry every time -> infinite loop
  if (node.type !== 'p') Transforms.setNodes(editor, { type: 'p' }, ...)
}

// after
const { normalizeNode } = editor
editor.normalizeNode = entry => {
  const [node, path] = entry
  if (Element.isElement(node) && node.type !== 'p') {
    Transforms.setNodes(editor, { type: 'p' }, { at: path })
    return
  }
  normalizeNode(entry)
}
Defensive patterns

Strategy: validation

Validate before calling

const { normalizeNode } = editor
editor.normalizeNode = entry => {
  const [node, path] = entry
  let handled = false
  // each fix must only run when the invalid state exists
  if (Element.isElement(node) && node.type == null) {
    Transforms.setNodes(editor, { type: 'paragraph' }, { at: path })
    handled = true
  }
  if (!handled) normalizeNode(entry)
}

Try / catch

try {
  Editor.normalize(editor, { force: true })
} catch (e) {
  if (/Could not completely normalize/.test(e.message)) {
    // log dirty paths, find ping-pong normalizers
  } else throw e
}

Prevention

When it happens

Trigger: A custom editor.normalizeNode override that fixes one thing but invalidates another (ping-pong normalization); withOverride/shouldNormalize plugins (e.g. slate-autoformat-style plugins) always returning true; a normalizeNode that calls editor.normalizeNode again or transforms nodes every pass; a normalize function that dirties the node it just normalized.

Common situations: Adding a normalization plugin whose fix triggers the next plugin's fix indefinitely; normalizations that insert nodes during normalization causing cascading dirty paths; upgrading Slate where normalizeNode chaining conventions changed (must call the previous normalizeNode for unmatched nodes).

Related errors


AI-assisted analysis of ianstormtaylor/slate@72a37c701e (2026-08-27). Data as JSON: /api/errors/f1b979f0215695e9. Report an issue: GitHub.