-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Add support for elements validation in DataForm's array fields #71194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
…lements using `findElementByLabel` instead of `isItemValid`.
…s are valid elements and streamline single-value checks for element validity.
… specified elements.
oandregal
left a comment
There was a problem hiding this 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 ); |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…ules and predefined options
Yes, I think there can be a |
…tion with custom and required checks
… isValid elements is passed
…les and predefined options
|
@mirka @oandregal I added a CleanShot.2025-08-22.at.11.09.55.mp4 |
packages/dataviews/src/validation.ts
Outdated
| ( field.type === 'integer' && | ||
| isEmptyNullOrUndefined( value ) ) || | ||
| ( field.type === 'array' && | ||
| isNotArrayEmptyOrContainsInvalidElements( value ) ) || |
There was a problem hiding this comment.
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?
| isNotArrayEmptyOrContainsInvalidElements( value ) ) || | |
| isArrayOrElementsEmptyNullOrUndefined( value ) ) || |
| } | ||
|
|
||
| if ( field?.elements ) { | ||
| if ( field?.elements && field.isValid?.elements ) { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
isItemValidutility: 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.elementsproperly (done for the array control) - make sure
isItemValidalso handles it (done)
Does this make sense, or am I missing any context here?
There was a problem hiding this comment.
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?
oandregal
left a comment
There was a problem hiding this 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
|
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. |
| // Create a validated FormTokenField wrapper | ||
| const ValidatedFormTokenControl = ( { |
There was a problem hiding this comment.
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 🙏
There was a problem hiding this comment.
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 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.mp4astronomy, and Astronomy will also be both accepted, but both will be displayed as "Astronomy" given the initial elements we pass |
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.movThe 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>
|
@oandregal oh yes, great catch! Looks like this is something in the EDIT: I feel like the EDIT 2: I pushed the changes to fix this in 6ef2a57 & fe03b3d. Working great now! Thanks for the pointers @oandregal ! |
…/gutenberg into add/elements-validation
|
@elazzabi could you also update the docs for validation to include |
| } else { | ||
| // Reset to initial state | ||
| setIncompleteTokenValue( '' ); | ||
| onInputChange( '' ); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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):



What?
This adds support for validation in DataForm's array fields. The following prop can be used to only accept input from the elements:
Why?
This allows the component to be customized depending on use cases.
How?
Testing Instructions
Add this to any Storybook implementation to test this.
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