Skip to content

Customizer selective refresh can fatal when component lookup returns null #4565

Description

@pirate-bot

Summary

Header or footer component selective refresh can terminate with Call to a member function get_id() on null.

Expected behavior: refreshing a Header Footer Grid component in the Customizer renders that component's markup.

Actual behavior: the refresh request can resolve no component and the wrapper dereferences it, producing a fatal error.

Impact: affected Customizer refresh requests fail instead of updating the preview.

Customer context

  • Product / area: Neve Header Footer Grid, Customizer selective refresh
  • Version: 4.2.10
  • Environment: WordPress 7.0.2; observed with PHP 8.2.32, 8.2.33, and 8.5.3-nmm1
  • Integration / third party: None identified
  • Reported error / symptom: Call to a member function get_id() on null during a frontend selective-refresh request
  • Impact: 3 occurrences across 3 production sites in the telemetry query window (2026-08-01 to 2026-08-04).

Reproduction notes

  1. In the WordPress Customizer, use Header Footer Grid components from more than one builder.
  2. Trigger a selective refresh for a component after another builder component has been rendered in the same request lifecycle.
  3. Observed production result: the partial-render request reaches the wrapper with no resolved component and fatals at get_id().

Production telemetry confirms the failing request path. The exact setting-change sequence and component IDs are unavailable.

Diagnosis

Conclusion

Production telemetry identifies the fatal at component-wrapper.php:13 during WordPress Customizer selective refresh. Repository inspection confirms that the wrapper dereferences current_component() without a null check, while the selective-refresh callback can leave a stale static builder component ID preferred over its own component ID. If that stale ID is absent from the callback's selected builder, get_component() returns null, matching the reported fatal.

Where this likely occurs

  • header-footer-grid/templates/component-wrapper.php — approx. lines 13-37, component wrapper template: calls current_component()->get_id() and several other instance methods without confirming that a component was resolved.
  • header-footer-grid/Core/Components/Abstract_Component.php — lines 604-612, Abstract_Component::customize_register(): registers each component's selective-refresh render_callback.
  • header-footer-grid/Core/Components/Abstract_Component.php — lines 622-630, Abstract_Component::render(): sets Abstract_Component::$current_component and Abstract_Builder::$current_builder, then loads the wrapper.
  • header-footer-grid/Core/Builder/Abstract_Builder.php — lines 1708-1714, Abstract_Builder::get_component(): when no explicit ID is supplied, it prefers Abstract_Builder::$current_component; an absent key resolves to null despite the method's nullable return contract.
  • header-footer-grid/Core/Builder/Abstract_Builder.php — lines 1456-1458 in Abstract_Builder::new_render_components() and lines 1660-1664 in Abstract_Builder::render_components(): normal rendering persists the static builder component ID that can be reused by a later lookup.
  • The unsafe wrapper is present at release tag v4.2.10, which matches the inspected HEAD release commit ba9965943.

Engineering notes

The reported stack reaches Abstract_Component::render() through WP_Customize_Partial::render, so the affected surface is Customizer selective refresh rather than ordinary full-page component rendering. HFG\current_component() in header-footer-grid/functions-template.php lines 56-65 delegates to the selected builder's get_component() without supplying the callback component ID. The observed cross-builder stale-state sequence is a code-path inference; telemetry confirms the resulting null dereference but does not identify the specific component pair. Neve Pro adds template locations but no component-wrapper.php override or alternate core Abstract_Component::render() path was found.

Test coverage status

No relevant PHP coverage was found during inspection for HFG\current_component(), Abstract_Builder::get_component(), the wrapper template, or a cross-builder selective-refresh sequence. Existing HFG Playwright coverage, including e2e-tests/specs/customizer/hfg/hfg-logo-component.spec.ts, hfg-search-component.spec.ts, and hfg-copyright-component.spec.ts, covers component settings or rendered output but not selective-refresh callback state resolution.

What to verify or explore next

  • May be worth reproducing in the Customizer by rendering one builder component before selectively refreshing a component registered to another builder.
  • If reproducible, checking the selective-refresh response and PHP warnings around an absent builder component key would establish the exact stale ID sequence.
  • Running the HFG Customizer Playwright suite after adding coverage for a cross-builder partial refresh would validate the affected request path.

Unknowns / follow-up

  • Telemetry does not include the specific component IDs, Customizer setting change, or persisted builder layout that led to the null lookup.
  • The inspected repository does not include WordPress core, so selective-refresh lifecycle details outside the captured stack are not locally verified.

Confidence

Confidence: 95/100

Production telemetry captures the fatal on three sites running Neve 4.2.10, and the inspected selective-refresh path can resolve a stale component ID against the selected builder and return null immediately before the reported dereference.

Crash telemetry

Occurrences 3
Distinct sites 3
First seen 2026-08-01 19:40 UTC
Last seen 2026-08-04 20:02 UTC
Crash location product:header-footer-grid/templates/component-wrapper.php:13
Request context frontend
Inside Themeisle SDK no
Product versions 4.2.10
WP versions 7.0.2
PHP versions 8.2.32, 8.2.33, 8.5.3-nmm1
SDK versions 3.3.59

Source: automated crash report — neve, fingerprint ea7bc15abbadc84d541c5d743698a4e1
Generated by bug-report-triage (ID: bug-report-triage_6a72d1811f8d79.15346783)

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions