Skip to content

Conversation

@elazzabi
Copy link
Contributor

What?

This adds support for validation in DataForm's array fields. The following prop can be used to only accept input from the elements:

isValid: {
	elements: true,
}

Why?

This allows the component to be customized depending on use cases.

How?

Testing Instructions

Add this to any Storybook implementation to test this.

isValid: {
	elements: true,
}

Testing Instructions for Keyboard

Screenshots or screencast

This is the only mode currently supported:

CleanShot.2025-08-13.at.15.31.54.mp4

This is the new mode:

CleanShot.2025-08-13.at.15.31.06.mp4

@elazzabi elazzabi requested a review from oandregal as a code owner August 13, 2025 14:34
@github-actions
Copy link

github-actions bot commented Aug 13, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: elazzabi <elazzabi@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@oandregal oandregal added [Type] Enhancement A suggestion for improvement. [Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond labels Aug 13, 2025
Copy link
Member

@oandregal oandregal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice progress here. I think the missing bit is how/whether the FormTokenField can display messages to users (validation errors) and/or signal invalid state via color, like other controls do. cc @mirka @jameskoster for thoughts

return false;
}
// For single-value fields, check if the value is a valid element
return validValues.includes( value );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to this, we need to display what's happening to the user.

Take the text control as an example. We handle isValid.required and isValid.custom there. By introducing isValid.elements we need to communicate that as well (we could use the customValidator function of the control to display something along the lines of "Value must be one of the elements".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an error when this happens
CleanShot 2025-09-11 at 09 30 27@2x

@mirka
Copy link
Member

mirka commented Aug 19, 2025

how/whether the FormTokenField can display messages to users (validation errors) and/or signal invalid state via color, like other controls do.

Yes, I think there can be a ValidatedFormTokenControl.

@elazzabi
Copy link
Contributor Author

@mirka @oandregal I added a ValidatedFormTokenControl to wrap the component with some messaging. I also added 2 validation examples to the Storybook. Here's the current behavior:

CleanShot.2025-08-22.at.11.09.55.mp4

( field.type === 'integer' &&
isEmptyNullOrUndefined( value ) ) ||
( field.type === 'array' &&
isNotArrayEmptyOrContainsInvalidElements( value ) ) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a small nit. For a second, I was confused for the use of "invalid elements" here, I thought why are we validating isValid.elements here if there's a specific check for it? Perhaps something along these lines makes that more clear?

Suggested change
isNotArrayEmptyOrContainsInvalidElements( value ) ) ||
isArrayOrElementsEmptyNullOrUndefined( value ) ) ||

}

if ( field?.elements ) {
if ( field?.elements && field.isValid?.elements ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given isValid.elements is implemented in validation.ts, do we still need it here? (same for any other field type)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this because, without it, the validation is run whenever the elements are present. Including when it is okay to add new elements

CleanShot.2025-08-22.at.15.23.13.mp4

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this code ever executed? I'd think we can remove it because it's covered by the control here, and so it never checks the custom validation here (that will run the same check again for elements).

There are a few levels to validation:

  • Control-level: the array control handles element validation, providing the corresponding message if it's invalid.
  • Form-level, via the isItemValid utility: this handles element validation (this is for form-level).

By introducing isValid.elements I understand that we can:

  • remove the existing checks in all field types (previously bundled as part of isValid.custom)
  • make sure every control handles isValid.elements properly (done for the array control)
  • make sure isItemValid also handles it (done)

Does this make sense, or am I missing any context here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After seeing it closely, I agree that this part was inadvertently executed (as I recorded in the video), but it should be removed altogether f2e301d

Now that it is removed, and regarding this part:

make sure every control handles isValid.elements properly (done for the array control)

I only see the select control that might benefit from such validation. Should I implement it there, too?

Copy link
Member

@oandregal oandregal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into this (adding two elements, the first in Titlecase the second in lowercase):

Screen.Recording.2025-08-22.at.14.43.37.mov

It doesn't happen in the FormToken component:

Screen.Recording.2025-08-22.at.14.45.58.mov

@oandregal
Copy link
Member

Hey @elazzabi this is really close, thanks for keep pushing. I think we need to solve a couple of glitches and it's ready to merge.

Comment on lines 13 to 14
// Create a validated FormTokenField wrapper
const ValidatedFormTokenControl = ( {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for being unclear, I meant we can add a ValidatedFormTokenControl component to @wordpress/components, along with the other ones 😄

Please open a separate PR for this addition so we can do a component review. And do let me know if you find anything lacking in the existing documentation 🙏

Copy link
Contributor Author

@elazzabi elazzabi Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh okay. @oandregal I'll implement this change ^ first before continuing, hopefully it will reduce the diff here 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mirka I created a PR for the component here #71350

Let me know if you have any feedback

@elazzabi
Copy link
Contributor Author

elazzabi commented Sep 11, 2025

I still see the issue with adding elements in different case (title vs lowercase):

@oandregal that is because the elements we pass to the field have a label of "Astronomy" and a value of "astronomy". So even if it's displayed as "Astronomy", the underlying value is lowercase, which is considered the same.

Here's how it works with "asia" and "Asia" (neither is part of the elements), for example. Both are accepted:

CleanShot.2025-09-11.at.09.56.18.mp4

astronomy, and Astronomy will also be both accepted, but both will be displayed as "Astronomy" given the initial elements we pass

@oandregal
Copy link
Member

I'm unable to clean the error message when the input loses focus

This sounds like we're clearing the input too early, before the values are committed? As a result, there's a discrepancy between the committed values in the component and what the validation sees. I'm only speculating based on testing the component, haven't looked at the code yet.

Note the difference (the countries component's input is cleared when I click outside, but the categories one doesn't):

Screen.Recording.2025-09-11.at.11.05.09.mov

The ValidatedFormToken doesn't do any clearing either:

Screen.Recording.2025-09-11.at.11.05.34.mov

Co-authored-by: André <583546+oandregal@users.noreply.github.com>
@elazzabi
Copy link
Contributor Author

elazzabi commented Sep 11, 2025

@oandregal oh yes, great catch! Looks like this is something in the FormTokenField component

EDIT: I feel like the FormTokenField component should call onInputChange after this reset, given the content has changed. This way, we can integrate the error clearing into the change event

EDIT 2: I pushed the changes to fix this in 6ef2a57 & fe03b3d. Working great now! Thanks for the pointers @oandregal !

@oandregal
Copy link
Member

oandregal commented Sep 11, 2025

@elazzabi could you also update the docs for validation to include isValid.elements?

} else {
// Reset to initial state
setIncompleteTokenValue( '' );
onInputChange( '' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify what is this for?

I'm not super familiar with the FormToken component, but this is not clearing internal state. It's triggering the onInputChange user callback when the component loses focus under certain conditions. It seems weird.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried testing the components themselves (both validated and the normal version) but I don't know how to trigger this path. It'd be great if you could share more about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

The FormToken component accepts a onInputChange prop that I think should be called when the input is changed. Here, when the user clicks outside the input, we setIncompleteTokenValue( '' ) but we were not letting the parent component know about the change.

In practice, this is how it looks without onInputChange:

CleanShot.2025-09-18.at.10.42.45.mp4

This is how it looks with it:

CleanShot.2025-09-18.at.10.39.45.mp4

(The error message is cleaned after navigating away)

In summary, we need to inform the parent about the input change to remove the error message.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I continued the conversation here #71194 (comment)

value={ arrayValue }
value={ arrayValueAsElements }
onChange={ onChangeControl }
onInputChange={ onInputChange }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, onValidate triggers for every onChange event. And the user is never able to introduce invalid data as per __experimentalValidateInput. So, the only case in which we ever display an error for isValid.elements is when the case in which the component loads with bad data (as here). Is this correct?

If so, why we need this? The custom error for elements will never happen as a result of an input event, so it doesn't need to be cleared when input is empty. Perhaps I'm not considering another use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason for this is to catch this edge case, where the input is cleared but the message stays:

CleanShot.2025-09-18.at.10.42.45.mp4

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elazzabi I've pushed 20410e4 to illustrate my point. It essentially removes everything related to input. We use __experimentalValidateInput to prevent an invalid element from being committed, but we don't trigger any validation on input.

I need to look at other things now, but there's this question we need to answer: does it makes sense to just remove onValidate / customValidity logic altogether? I think the only case where this may be useful is when the component is loaded with data that is not part of the element list.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only case where this may be useful is when the component is loaded with data that is not part of the element list.

So I tested this scenario now by adding es to the data that gets loaded. This is the result:

Screen.Recording.2025-09-19.at.17.57.12.mov

I think we can keep that logic, but the message needs to be more specific. Along the lines of: Please select from the available options: "es" is invalid..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the commit @oandregal !

In this case, we are completely removing the feedback when the user tries to insert an element via "," (ie nothing happens), but I'm okay keeping it that way.

I pushed a change to update the message with two versions (singular and plural):
CleanShot 2025-09-19 at 17 47 41@2x
CleanShot 2025-09-19 at 17 48 07@2x

@oandregal oandregal merged commit 3327390 into WordPress:trunk Sep 23, 2025
68 checks passed
@github-actions github-actions bot added this to the Gutenberg 21.8 milestone Sep 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants