Skip to content

[Bug]: Redundant mount useEffect calls setAttributes and dirties blocks on initial load #33

Description

@dfross

Summary

In the Gutenberg block integration (gutenberg/index.js), an initial render lifecycle bug causes clean posts, templates, and template parts to be marked as modified ("Review changes") immediately upon opening the WordPress Site Editor or Block Editor.

Affected surface

Gutenberg

Steps to reproduce

  1. Install Core Framework and enable the Gutenberg integration.
  2. Open any page or template part in the Site Editor (site-editor.php).
  3. Observe that without clicking or typing anything, the editor shows unsaved changes ("Review 1 change").
  4. Inspecting wp.data.select('core').getEntityRecordNonTransientEdits(...) shows that block className attributes were modified during initial page load.

Expected behavior

Opening a clean page, template, or template part in the editor should not trigger unsaved changes or mark blocks modified until the user actually interacts with the controls or edits content.

Actual behavior

Opening any page or template immediately flags blocks as modified. A mount useEffect in gutenberg/index.js fires on initial render before state populates, calling setAttributes({ className: "" }) and wiping/rewriting classes in Gutenberg's in-memory store.

Core Framework version or commit

2.0.2

Environment

WordPress 7.1, PHP 8.4.23, Gutenberg / Site Editor (FSE), Chrome (any modern browser)

Logs or screenshots

// Root cause in gutenberg/index.js:
const [h, m] = useState({ active: [], dynamic: [] });

// Because h initializes as empty ({ active: [], dynamic: [] }), this effect runs on initial mount:
useEffect(() => {
r.setAttributes({ className: ... });
}, [h]);

// This mutates Gutenberg's entity record during initial render, creating spurious unsaved changes.
// Fix: Remove this useEffect. Sidebar click handlers already dispatch setAttributes() directly when the user toggles classes.

Root cause

In gutenberg/index.js, the component initializes internal state with empty arrays:

const [h, m] = useState({ active: [], dynamic: [] });
A useEffect hook depends on [h] and synchronizes it to Gutenberg via r.setAttributes(...):

javascript

useEffect(() => {
    // On mount, h is empty ({ active: [], dynamic: [] })
    // This evaluates to an empty string and calls setAttributes({ className: "" })
    // before the actual classes populate, dirtying the block on mount.
    r.setAttributes({ className: ... });
}, [h]);

Because h starts empty, this effect fires on initial mount and writes to the block attributes before the saved classes finish loading. Calling setAttributes() during mount mutates Gutenberg's entity store and creates spurious unsaved changes.

Furthermore, this mount effect is redundant because Core Framework's click handlers already call setAttributes() directly when a user interacts with class controls in the sidebar.

Proposed fix
Remove the useEffect that calls setAttributes based on [h]. Class updates should only be dispatched when the user explicitly clicks or toggles a class in the inspector panel.

Affected file:
wp-content/plugins/core-framework/gutenberg/index.js (or source file before build)

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions