Skip to content

EuiValidatableControl: useEffect for setCustomValidity causes race with native form validation #9686

@ghudgins

Description

@ghudgins

Warning: THIS WAS VIBE TROUBLESHOT BUT THE LLM SAYS IT IS, AND I QUOTE, "VERY CONFIDENT"
Context from Kibana issue....apologies to those not in Elastic for not being able to see this

EuiValidatableControl uses useEffect to call setCustomValidity('Invalid') on the underlying DOM element when isInvalid changes. Because useEffect runs after paint, there is a one-frame window where the DOM element's validity is stale relative to React state. If a form submit (via type="submit" button) fires during this window, the browser's native constraint validation sees the stale customValidity and shows a native "Invalid" tooltip—even though React has already cleared isInvalid.

Relevant code (validatable_control.js):

useEffect(function () {
  if (controlEl == null || typeof controlEl.setCustomValidity !== 'function') return;
  if (isInvalid) {
    controlEl.setCustomValidity('Invalid');
  } else {
    controlEl.setCustomValidity('');
  }
}, [isInvalid, controlEl]);

Suggested fix: Replace useEffect with useLayoutEffect, which runs synchronously before paint:

useLayoutEffect(function () {
  if (controlEl == null || typeof controlEl.setCustomValidity !== 'function') return;
  if (isInvalid) {
    controlEl.setCustomValidity('Invalid');
  } else {
    controlEl.setCustomValidity('');
  }
}, [isInvalid, controlEl]);

This closes the timing gap between React state and DOM validity state, preventing stale native validation tooltips.

Affects: Any EUI form control (EuiFieldText, EuiTextArea, etc.) inside a <form> without noValidate that toggles isInvalid during user interaction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions