Skip to content
Prev Previous commit
Next Next commit
Wait for async response before showing local state
  • Loading branch information
mirka committed Aug 22, 2025
commit 8f4198b47b6e7c77fad30a232cca059ed089a84a
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
/**
* WordPress dependencies
*/
import { usePrevious } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

/**
* External dependencies
*/
import {
cloneElement,
forwardRef,
Expand Down Expand Up @@ -98,6 +95,7 @@ function UnforwardedControlWithError< C extends React.ReactElement >(
| undefined
>();
const [ isTouched, setIsTouched ] = useState( false );
const previousCustomValidityType = usePrevious( customValidity?.type );

// Ensure that error messages are visible after user attemps to submit a form
// with multiple invalid fields.
Expand Down Expand Up @@ -135,7 +133,7 @@ function UnforwardedControlWithError< C extends React.ReactElement >(
// Wait before showing a validating state.
const timer = setTimeout( () => {
validityTarget?.setCustomValidity( '' );
setErrorMessage( validityTarget?.validationMessage );
setErrorMessage( undefined );

setStatusMessage( {
type: 'validating',
Expand All @@ -146,6 +144,12 @@ function UnforwardedControlWithError< C extends React.ReactElement >(
return () => clearTimeout( timer );
}
case 'valid': {
// Ensures that we wait for any async responses before showing
// a synchronously valid state.
if ( previousCustomValidityType === 'valid' ) {
break;
}

validityTarget?.setCustomValidity( '' );
setErrorMessage( validityTarget?.validationMessage );

Expand All @@ -170,27 +174,21 @@ function UnforwardedControlWithError< C extends React.ReactElement >(
customValidity?.type,
customValidity?.message,
getValidityTarget,
previousCustomValidityType,
] );

const onBlur = ( event: React.FocusEvent< HTMLDivElement > ) => {
if ( isTouched ) {
return;
}

// Only consider "blurred from the component" if focus has fully left the wrapping div.
// This prevents unnecessary blurs from components with multiple focusable elements.
if (
! event.relatedTarget ||
! event.currentTarget.contains( event.relatedTarget )
) {
setIsTouched( true );

const validityTarget = getValidityTarget();

// Prevents a double flash of the native error tooltip when the control is already showing one.
if ( ! validityTarget?.validity.valid ) {
if ( ! errorMessage ) {
setErrorMessage( validityTarget?.validationMessage );
}
return;
}

onValidate?.();
}
};
Expand Down
Loading