Skip to content
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

[WIP/Exploration]: DataForm validation/form field state #67357

Draft
wants to merge 14 commits into
base: add/use-data-form-pattern-creation
Choose a base branch
from

Conversation

gigitux
Copy link
Contributor

@gigitux gigitux commented Nov 27, 2024

Related #59745

What?

This PR presents a proof of concept for handling error messages and validation in Dataform.

Validation schema on the field level

The validation schema is at the field level. The current shape of the validationSchema is a POC, but the idea is that we will use adopt the same structure used by #67324.

Consumers can decide when to show the message error via the onTouched value.

useValidation hook

By default, validation works smoothly with the validationSchema, but consumers might need more control over the form validation state. To address this, the useValidation hook can now be used, allowing consumers to pass a validation object to the dataform component.

This enhancement enables consumers to:

  • Manually set errors (e.g., to invalidate a field after a backend request).
  • Check the form’s validity status
  • Monitor touched fields.

Old approach:

### `useForm` hook

For this reason, I created a useForm hook. This hook allows consumers to define business logic of their form:

const form = useForm( {
title: {
validation: {
validateWhenDirty: false,
callback: ( data ) => {
if ( data.title.length === 0 ) {
return {
isValid: false,
errorMessage: 'Title is required',
};
}
if ( data.title.length > 5 ) {
return {
isValid: false,
errorMessage: 'Title is too long',
};
}
return {
isValid: true,
errorMessage: undefined,
};
},
},
},
} );

useForm hook returns an object. This object needs to be passed to the DataForm component as prop form.

<DataForm
data={ pattern }
fields={ fields }
form={ form }
onChange={ ( newData ) => {
setPattern( {
...pattern,
...newData,
} );
} }
/>

This hook returns utilities and state for advanced logic. However, for simple use cases, dataform will take care of everything based on the validation object passed to the hook. For each id, the consumer can specify:

  • showErrorOnlyWhenDirty-> the error message will be passed to the layout ONLY when the input has been touched.
  • callback -> you can return the validity of the field and the error message to render if the validation doesn't pass.

validation: {
showErrorOnlyWhenDirty: true,
callback: ( data ) => {
if ( data.title.length === 0 ) {
return {
isValid: false,
errorMessage: 'Title is required',
};
}
if ( data.title.length > 5 ) {
return {
isValid: false,
errorMessage: 'Title is too long',
};
}
return {
isValid: true,
errorMessage: undefined,
};
},
},
},
} );

Furthermore, the consumer can compute the current validity of the form using the isFormValid function.

aria-disabled={ ! form.isFormValid( pattern ) }
isBusy={ isSaving }

Field

The field receives an additional prop called errorMessage, which can be either a string or undefined. Based on this value, the field can render an error message.

https://github.com/WordPress/gutenberg/blob/e01fb7f7aec7990ae6aabba0f574bfbabab35ad5/packages/dataviews/src/dataform-controls/text.tsx#L12-L47

Demo

Screen.Capture.on.2024-11-28.at.15-10-45.mp4

Note

API names are still WIP. This PR is mostly to gathering feedback and explore more this approach with more complex use-case.

Why?

How?

Testing Instructions

Testing Instructions for Keyboard

Screenshots or screencast

Copy link

github-actions bot commented Nov 27, 2024

Size Change: +1.73 kB (+0.09%)

Total Size: 1.84 MB

Filename Size Change
build/edit-site/index.min.js 220 kB +631 B (+0.29%)
build/editor/index.min.js 114 kB +579 B (+0.51%)
build/patterns/index.min.js 10.3 kB +516 B (+5.28%) 🔍
ℹ️ View Unchanged
Filename Size
build-module/a11y/index.min.js 482 B
build-module/block-library/file/view.min.js 447 B
build-module/block-library/image/view.min.js 1.78 kB
build-module/block-library/navigation/view.min.js 1.16 kB
build-module/block-library/query/view.min.js 742 B
build-module/block-library/search/view.min.js 616 B
build-module/interactivity-router/index.min.js 3.03 kB
build-module/interactivity/debug.min.js 17.2 kB
build-module/interactivity/index.min.js 13.6 kB
build/a11y/index.min.js 952 B
build/annotations/index.min.js 2.26 kB
build/api-fetch/index.min.js 2.32 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 579 B
build/block-directory/index.min.js 7.26 kB
build/block-directory/style-rtl.css 1 kB
build/block-directory/style.css 1 kB
build/block-editor/content-rtl.css 4.47 kB
build/block-editor/content.css 4.46 kB
build/block-editor/default-editor-styles-rtl.css 394 B
build/block-editor/default-editor-styles.css 394 B
build/block-editor/index.min.js 259 kB
build/block-editor/style-rtl.css 15.8 kB
build/block-editor/style.css 15.7 kB
build/block-library/blocks/archives/editor-rtl.css 84 B
build/block-library/blocks/archives/editor.css 83 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 149 B
build/block-library/blocks/audio/editor.css 151 B
build/block-library/blocks/audio/style-rtl.css 132 B
build/block-library/blocks/audio/style.css 132 B
build/block-library/blocks/audio/theme-rtl.css 134 B
build/block-library/blocks/audio/theme.css 134 B
build/block-library/blocks/avatar/editor-rtl.css 115 B
build/block-library/blocks/avatar/editor.css 115 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/button/editor-rtl.css 265 B
build/block-library/blocks/button/editor.css 265 B
build/block-library/blocks/button/style-rtl.css 555 B
build/block-library/blocks/button/style.css 555 B
build/block-library/blocks/buttons/editor-rtl.css 291 B
build/block-library/blocks/buttons/editor.css 291 B
build/block-library/blocks/buttons/style-rtl.css 345 B
build/block-library/blocks/buttons/style.css 345 B
build/block-library/blocks/calendar/style-rtl.css 240 B
build/block-library/blocks/calendar/style.css 240 B
build/block-library/blocks/categories/editor-rtl.css 132 B
build/block-library/blocks/categories/editor.css 131 B
build/block-library/blocks/categories/style-rtl.css 152 B
build/block-library/blocks/categories/style.css 152 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 139 B
build/block-library/blocks/code/style.css 139 B
build/block-library/blocks/code/theme-rtl.css 122 B
build/block-library/blocks/code/theme.css 122 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 420 B
build/block-library/blocks/columns/style.css 420 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 124 B
build/block-library/blocks/comment-author-avatar/editor.css 124 B
build/block-library/blocks/comment-author-name/style-rtl.css 72 B
build/block-library/blocks/comment-author-name/style.css 72 B
build/block-library/blocks/comment-content/style-rtl.css 120 B
build/block-library/blocks/comment-content/style.css 120 B
build/block-library/blocks/comment-date/style-rtl.css 65 B
build/block-library/blocks/comment-date/style.css 65 B
build/block-library/blocks/comment-edit-link/style-rtl.css 70 B
build/block-library/blocks/comment-edit-link/style.css 70 B
build/block-library/blocks/comment-reply-link/style-rtl.css 71 B
build/block-library/blocks/comment-reply-link/style.css 71 B
build/block-library/blocks/comment-template/style-rtl.css 200 B
build/block-library/blocks/comment-template/style.css 199 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 238 B
build/block-library/blocks/comments-pagination/editor.css 231 B
build/block-library/blocks/comments-pagination/style-rtl.css 245 B
build/block-library/blocks/comments-pagination/style.css 241 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 837 B
build/block-library/blocks/comments/editor.css 836 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 637 B
build/block-library/blocks/cover/editor-rtl.css 631 B
build/block-library/blocks/cover/editor.css 631 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 86 B
build/block-library/blocks/details/style.css 86 B
build/block-library/blocks/embed/editor-rtl.css 331 B
build/block-library/blocks/embed/editor.css 331 B
build/block-library/blocks/embed/style-rtl.css 419 B
build/block-library/blocks/embed/style.css 419 B
build/block-library/blocks/embed/theme-rtl.css 133 B
build/block-library/blocks/embed/theme.css 133 B
build/block-library/blocks/file/editor-rtl.css 326 B
build/block-library/blocks/file/editor.css 326 B
build/block-library/blocks/file/style-rtl.css 278 B
build/block-library/blocks/file/style.css 279 B
build/block-library/blocks/footnotes/style-rtl.css 198 B
build/block-library/blocks/footnotes/style.css 197 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 229 B
build/block-library/blocks/form-input/style-rtl.css 357 B
build/block-library/blocks/form-input/style.css 357 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 344 B
build/block-library/blocks/form-submission-notification/editor.css 341 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/form/view.min.js 470 B
build/block-library/blocks/freeform/editor-rtl.css 2.6 kB
build/block-library/blocks/freeform/editor.css 2.6 kB
build/block-library/blocks/gallery/editor-rtl.css 946 B
build/block-library/blocks/gallery/editor.css 951 B
build/block-library/blocks/gallery/style-rtl.css 1.83 kB
build/block-library/blocks/gallery/style.css 1.82 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 334 B
build/block-library/blocks/group/editor.css 334 B
build/block-library/blocks/group/style-rtl.css 103 B
build/block-library/blocks/group/style.css 103 B
build/block-library/blocks/group/theme-rtl.css 79 B
build/block-library/blocks/group/theme.css 79 B
build/block-library/blocks/heading/style-rtl.css 188 B
build/block-library/blocks/heading/style.css 188 B
build/block-library/blocks/html/editor-rtl.css 346 B
build/block-library/blocks/html/editor.css 347 B
build/block-library/blocks/image/editor-rtl.css 799 B
build/block-library/blocks/image/editor.css 799 B
build/block-library/blocks/image/style-rtl.css 1.6 kB
build/block-library/blocks/image/style.css 1.59 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/latest-comments/style-rtl.css 355 B
build/block-library/blocks/latest-comments/style.css 354 B
build/block-library/blocks/latest-posts/editor-rtl.css 139 B
build/block-library/blocks/latest-posts/editor.css 138 B
build/block-library/blocks/latest-posts/style-rtl.css 509 B
build/block-library/blocks/latest-posts/style.css 510 B
build/block-library/blocks/list/style-rtl.css 107 B
build/block-library/blocks/list/style.css 107 B
build/block-library/blocks/loginout/style-rtl.css 61 B
build/block-library/blocks/loginout/style.css 61 B
build/block-library/blocks/media-text/editor-rtl.css 321 B
build/block-library/blocks/media-text/editor.css 320 B
build/block-library/blocks/media-text/style-rtl.css 552 B
build/block-library/blocks/media-text/style.css 550 B
build/block-library/blocks/more/editor-rtl.css 427 B
build/block-library/blocks/more/editor.css 427 B
build/block-library/blocks/navigation-link/editor-rtl.css 644 B
build/block-library/blocks/navigation-link/editor.css 645 B
build/block-library/blocks/navigation-link/style-rtl.css 192 B
build/block-library/blocks/navigation-link/style.css 191 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 295 B
build/block-library/blocks/navigation-submenu/editor.css 294 B
build/block-library/blocks/navigation/editor-rtl.css 2.2 kB
build/block-library/blocks/navigation/editor.css 2.2 kB
build/block-library/blocks/navigation/style-rtl.css 2.24 kB
build/block-library/blocks/navigation/style.css 2.23 kB
build/block-library/blocks/nextpage/editor-rtl.css 392 B
build/block-library/blocks/nextpage/editor.css 392 B
build/block-library/blocks/page-list/editor-rtl.css 378 B
build/block-library/blocks/page-list/editor.css 378 B
build/block-library/blocks/page-list/style-rtl.css 192 B
build/block-library/blocks/page-list/style.css 192 B
build/block-library/blocks/paragraph/editor-rtl.css 236 B
build/block-library/blocks/paragraph/editor.css 236 B
build/block-library/blocks/paragraph/style-rtl.css 341 B
build/block-library/blocks/paragraph/style.css 340 B
build/block-library/blocks/post-author-biography/style-rtl.css 74 B
build/block-library/blocks/post-author-biography/style.css 74 B
build/block-library/blocks/post-author-name/style-rtl.css 69 B
build/block-library/blocks/post-author-name/style.css 69 B
build/block-library/blocks/post-author/editor-rtl.css 107 B
build/block-library/blocks/post-author/editor.css 107 B
build/block-library/blocks/post-author/style-rtl.css 188 B
build/block-library/blocks/post-author/style.css 189 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 527 B
build/block-library/blocks/post-comments-form/style.css 528 B
build/block-library/blocks/post-content/style-rtl.css 61 B
build/block-library/blocks/post-content/style.css 61 B
build/block-library/blocks/post-date/style-rtl.css 62 B
build/block-library/blocks/post-date/style.css 62 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 155 B
build/block-library/blocks/post-excerpt/style.css 155 B
build/block-library/blocks/post-featured-image/editor-rtl.css 729 B
build/block-library/blocks/post-featured-image/editor.css 726 B
build/block-library/blocks/post-featured-image/style-rtl.css 347 B
build/block-library/blocks/post-featured-image/style.css 347 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 399 B
build/block-library/blocks/post-template/style.css 398 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 70 B
build/block-library/blocks/post-time-to-read/style.css 70 B
build/block-library/blocks/post-title/style-rtl.css 162 B
build/block-library/blocks/post-title/style.css 162 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 134 B
build/block-library/blocks/pullquote/editor.css 134 B
build/block-library/blocks/pullquote/style-rtl.css 351 B
build/block-library/blocks/pullquote/style.css 350 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 121 B
build/block-library/blocks/query-pagination-numbers/editor.css 118 B
build/block-library/blocks/query-pagination/editor-rtl.css 154 B
build/block-library/blocks/query-pagination/editor.css 154 B
build/block-library/blocks/query-pagination/style-rtl.css 237 B
build/block-library/blocks/query-pagination/style.css 237 B
build/block-library/blocks/query-title/style-rtl.css 64 B
build/block-library/blocks/query-title/style.css 64 B
build/block-library/blocks/query/editor-rtl.css 527 B
build/block-library/blocks/query/editor.css 527 B
build/block-library/blocks/quote/style-rtl.css 238 B
build/block-library/blocks/quote/style.css 238 B
build/block-library/blocks/quote/theme-rtl.css 233 B
build/block-library/blocks/quote/theme.css 236 B
build/block-library/blocks/read-more/style-rtl.css 138 B
build/block-library/blocks/read-more/style.css 138 B
build/block-library/blocks/rss/editor-rtl.css 101 B
build/block-library/blocks/rss/editor.css 101 B
build/block-library/blocks/rss/style-rtl.css 288 B
build/block-library/blocks/rss/style.css 287 B
build/block-library/blocks/search/editor-rtl.css 199 B
build/block-library/blocks/search/editor.css 199 B
build/block-library/blocks/search/style-rtl.css 660 B
build/block-library/blocks/search/style.css 658 B
build/block-library/blocks/search/theme-rtl.css 113 B
build/block-library/blocks/search/theme.css 113 B
build/block-library/blocks/separator/editor-rtl.css 100 B
build/block-library/blocks/separator/editor.css 100 B
build/block-library/blocks/separator/style-rtl.css 248 B
build/block-library/blocks/separator/style.css 248 B
build/block-library/blocks/separator/theme-rtl.css 195 B
build/block-library/blocks/separator/theme.css 195 B
build/block-library/blocks/shortcode/editor-rtl.css 286 B
build/block-library/blocks/shortcode/editor.css 286 B
build/block-library/blocks/site-logo/editor-rtl.css 806 B
build/block-library/blocks/site-logo/editor.css 803 B
build/block-library/blocks/site-logo/style-rtl.css 218 B
build/block-library/blocks/site-logo/style.css 218 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-tagline/style-rtl.css 65 B
build/block-library/blocks/site-tagline/style.css 65 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/site-title/style-rtl.css 143 B
build/block-library/blocks/site-title/style.css 143 B
build/block-library/blocks/social-link/editor-rtl.css 309 B
build/block-library/blocks/social-link/editor.css 309 B
build/block-library/blocks/social-links/editor-rtl.css 729 B
build/block-library/blocks/social-links/editor.css 727 B
build/block-library/blocks/social-links/style-rtl.css 1.51 kB
build/block-library/blocks/social-links/style.css 1.5 kB
build/block-library/blocks/spacer/editor-rtl.css 346 B
build/block-library/blocks/spacer/editor.css 346 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table-of-contents/style-rtl.css 83 B
build/block-library/blocks/table-of-contents/style.css 83 B
build/block-library/blocks/table/editor-rtl.css 394 B
build/block-library/blocks/table/editor.css 394 B
build/block-library/blocks/table/style-rtl.css 640 B
build/block-library/blocks/table/style.css 639 B
build/block-library/blocks/table/theme-rtl.css 152 B
build/block-library/blocks/table/theme.css 152 B
build/block-library/blocks/tag-cloud/editor-rtl.css 144 B
build/block-library/blocks/tag-cloud/editor.css 144 B
build/block-library/blocks/tag-cloud/style-rtl.css 266 B
build/block-library/blocks/tag-cloud/style.css 265 B
build/block-library/blocks/template-part/editor-rtl.css 368 B
build/block-library/blocks/template-part/editor.css 368 B
build/block-library/blocks/template-part/theme-rtl.css 113 B
build/block-library/blocks/template-part/theme.css 113 B
build/block-library/blocks/term-description/style-rtl.css 126 B
build/block-library/blocks/term-description/style.css 126 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 165 B
build/block-library/blocks/text-columns/style.css 165 B
build/block-library/blocks/verse/style-rtl.css 98 B
build/block-library/blocks/verse/style.css 98 B
build/block-library/blocks/video/editor-rtl.css 441 B
build/block-library/blocks/video/editor.css 442 B
build/block-library/blocks/video/style-rtl.css 192 B
build/block-library/blocks/video/style.css 192 B
build/block-library/blocks/video/theme-rtl.css 134 B
build/block-library/blocks/video/theme.css 134 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.08 kB
build/block-library/common.css 1.08 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.8 kB
build/block-library/editor.css 11.8 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 222 kB
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 15 kB
build/block-library/style.css 15 kB
build/block-library/theme-rtl.css 708 B
build/block-library/theme.css 712 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 53 kB
build/commands/index.min.js 16.1 kB
build/commands/style-rtl.css 955 B
build/commands/style.css 952 B
build/components/index.min.js 228 kB
build/components/style-rtl.css 12.4 kB
build/components/style.css 12.4 kB
build/compose/index.min.js 12.7 kB
build/core-commands/index.min.js 3.09 kB
build/core-data/index.min.js 74.3 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.35 kB
build/customize-widgets/style.css 1.35 kB
build/data-controls/index.min.js 641 B
build/data/index.min.js 8.69 kB
build/date/index.min.js 18 kB
build/deprecated/index.min.js 458 B
build/dom-ready/index.min.js 325 B
build/dom/index.min.js 4.66 kB
build/edit-post/classic-rtl.css 578 B
build/edit-post/classic.css 580 B
build/edit-post/index.min.js 13.3 kB
build/edit-post/style-rtl.css 2.75 kB
build/edit-post/style.css 2.75 kB
build/edit-site/posts-rtl.css 7.58 kB
build/edit-site/posts.css 7.58 kB
build/edit-site/style-rtl.css 13.8 kB
build/edit-site/style.css 13.8 kB
build/edit-widgets/index.min.js 17.7 kB
build/edit-widgets/style-rtl.css 4.09 kB
build/edit-widgets/style.css 4.09 kB
build/editor/style-rtl.css 9.25 kB
build/editor/style.css 9.25 kB
build/element/index.min.js 4.82 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 8.05 kB
build/format-library/style-rtl.css 476 B
build/format-library/style.css 476 B
build/hooks/index.min.js 1.65 kB
build/html-entities/index.min.js 445 B
build/i18n/index.min.js 3.58 kB
build/is-shallow-equal/index.min.js 526 B
build/keyboard-shortcuts/index.min.js 1.31 kB
build/keycodes/index.min.js 1.46 kB
build/list-reusable-blocks/index.min.js 2.13 kB
build/list-reusable-blocks/style-rtl.css 852 B
build/list-reusable-blocks/style.css 852 B
build/media-utils/index.min.js 3.58 kB
build/notices/index.min.js 946 B
build/nux/index.min.js 1.62 kB
build/nux/style-rtl.css 749 B
build/nux/style.css 745 B
build/patterns/style-rtl.css 687 B
build/patterns/style.css 685 B
build/plugins/index.min.js 1.86 kB
build/preferences-persistence/index.min.js 2.06 kB
build/preferences/index.min.js 2.9 kB
build/preferences/style-rtl.css 554 B
build/preferences/style.css 554 B
build/primitives/index.min.js 829 B
build/priority-queue/index.min.js 1.54 kB
build/private-apis/index.min.js 972 B
build/react-i18n/index.min.js 630 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 6.76 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/index.min.js 2.55 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.3 kB
build/router/index.min.js 5.42 kB
build/server-side-render/index.min.js 1.94 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 2.04 kB
build/token-list/index.min.js 581 B
build/url/index.min.js 3.9 kB
build/vendors/react-dom.min.js 41.7 kB
build/vendors/react-jsx-runtime.min.js 556 B
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 965 B
build/vips/index.min.js 36.2 kB
build/warning/index.min.js 250 B
build/widgets/index.min.js 7.16 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.03 kB

compressed-size-action

@gigitux gigitux changed the title [WIP/Exploration]: DataForm validation [WIP/Exploration]: DataForm validation/form field state Nov 28, 2024
@gigitux gigitux changed the base branch from trunk to add/use-data-form-pattern-creation November 28, 2024 14:19
Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

Great start, left some thoughts.

hideLabelFromVision={ hideLabelFromVision }
/>
{ errorMessage && (
<p className="dataform-control-error">{ errorMessage }</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel most of the UI libraries in React support error messages and validation states in the UI components directly. Maybe this is an opportunity to start iterating on these things in the UI components directly (to avoid bifurcation if used outside dataform)

cc @mirka @ciampo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

💯 on this!

Copy link
Contributor

Choose a reason for hiding this comment

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

Just FYI: I think our components friends would prefer a dedicated PR for 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.

Yeah, it is already in my plan. This is mostly a POC. Happy to help! 💪

fields: supportedFields,
touchedFields: [] as string[],
messageErrors: {},
} );
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels like a bit of a complex state, useReducer might prove to be better in the long term.

setTouchedFields,
setErrors,
isFormValid,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Some of these are used as dependencies in hooks later... so probably better to memoize (useEvent/useCallback)

@@ -28,6 +28,7 @@ export function DataFormLayout< Item >( {
field: FormField;
onChange: ( value: any ) => void;
hideLabelFromVision?: boolean;
errorMessage: string | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if I see this as a "field" property or a "field layout" property.

messageErrors: Record< string, string | undefined >;
setTouchedFields: ( touchedFields: string[] ) => void;
setErrors: ( field: string, error: string | undefined ) => void;
isFormValid: ( data: Record< string, any > ) => boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is mixing several things together. form for me is about the "layout" of the form, how do we render the form. I think validation state should probably be separate from it. Maybe the current form prop name is what pushed you to add this stuff here and we might want to consider renaming it "view" or something like that.

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 don't have a strong opinion. IMHO form could be a big object with layout and validation option or spread them across multiple props.

title: {
validation: {
showErrorOnlyWhenDirty: true,
callback: ( data ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel this function should ideally be "absorbed" in a declarative way ultimately by having things like minLengh, maxLength, isRequired... in the field definition maybe.

Copy link
Contributor Author

@gigitux gigitux Nov 29, 2024

Choose a reason for hiding this comment

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

This means that what I wrote in the PR description isn't completely true:

The primary consideration is that fields should contain only UI logic, while business logic should reside in the consumer codebase. Consequently, the isValid callback should not be implemented at the field level.

I would avoid implementing validation on the field level, mostly I feel that different consumers could need different validation.

EDIT: Mmmm, I think that there is a nuance here. I feel that it makes sense that @wordpress/fields are opinionated, but I can imagine that consumers would like to override validation rules.

Should we support validation on both side:

  • field level
  • consumer level

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Regarding the “declarative way,” we should support a generic function, allowing consumers to choose their preferred validation library.

However, we could use a declarative approach once Gutenberg adopts its own validation library (see: #67324).

Additionally, it’s important to note that implementing validation at the field level restricts validation to that specific field. It’s common to encounter scenarios where a field’s valid status depends on the value of another field.

Copy link
Contributor

Choose a reason for hiding this comment

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

There are two questions:

  • Should it be field level, for and given the discussion in Proposal: JSON Schema validation in Gutenberg #67324 and the relationship with JSON schema, I think the field level would have my preference for now. We probably need more advanced multi-field validation later but that's for another day :P
  • Should we start by the declarative approach or the escape hatch approach. I think for me this is still unclear to answer right now. If we go declarative, we need to try to match JSON schema. The con is it involves a bit more "architecture" work on the framework side.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesn't set validation at the field level prevents consumers from applying custom validation to generic fields like “text” or “date”? They will need to create custom fields instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

We probably need more advanced multi-field validation later but that's for another day :P

How would that look? Schema (with $data) already supports multiple fields. All client side validation relates to a field in the form, so ultimately, all errors should point to the field that caused the error.

Errors that can't be linked to a field probably come from server or before reaching the form, and those might be related to the form UI I think.

Copy link
Contributor

@youknowriad youknowriad Nov 29, 2024

Choose a reason for hiding this comment

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

Doesn't set validation at the field level prevents consumers from applying custom validation to generic fields like “text” or “date”? They will need to create custom fields instead.

I don't understand how it would prevent anything? Here's a rough idea how I see things

const fields = [ {
   id: 'title',
   type: 'string',
   minLength: 10,
   maxLength: 50,
   isRequired: true,
} ]

Copy link
Contributor

Choose a reason for hiding this comment

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

Errors that can't be linked to a field probably come from server or before reaching the form, and those might be related to the form UI I think.

I don't know yet how it would look, I need use-cases to be able to think about it first but yeah I think forms can probably have global errors that are not directly attached to fields

Copy link
Contributor Author

@gigitux gigitux Dec 2, 2024

Choose a reason for hiding this comment

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

I don't understand how it would prevent anything? Here's a rough idea how I see things

Oooh, ok. I was convicted that you were referring to putting validation constraint in the field declaration. Sorry for the confusion.

We could follow this approach, especially given the discussion in WordPress Gutenberg #67324. However, we should also allow users to define custom callbacks if they prefer a different validation library or need to perform complex validations, such as multi-field validation.

The key point is to define the validation state on the consumer side and pass it to the dataform. This ensures that both the library and the consumer can update the state as needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oooh, ok. I was convicted that you were referring to putting validation constraint in the field declaration. Sorry for the confusion.

Yes, that example is a field.

We could follow this approach, especially given the discussion in #67324. However, we should also allow users to define custom callbacks if they prefer a different validation library or need to perform complex validations, such as multi-field validation.

Maybe, but my main point is that this is an edge case and we should treat it once we face it as otherwise, I'm not sure we should be providing the right solution.

@youknowriad youknowriad added [Type] Experimental Experimental feature or API. [Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond labels Nov 29, 2024
@gigitux
Copy link
Contributor Author

gigitux commented Nov 29, 2024

Thanks for your precious feedback, @youknowriad! 🙇

@senadir
Copy link
Contributor

senadir commented Nov 29, 2024

@gigitux is there any reason we're coupling the form this early? I do agree with you that form fields and form in general only worry about UI but we're now passing validation functions, which I believe is too much coupling? I believe it was discussed before but shouldn't you run validation outside, assuming the form state can be accessed outside, you should just do whatever logic you want and return an error object of field id and errors. This allows client side validation, server validation, and whatever logic you want.

In your case, you cannot for example hydrate the form with existing errors if needed (if it already has values), since you need to run validate on touch/blur. There's also no way to mark a field invalid from outside the validation function (which has its runtime controlled by the dataform), this means you can't trigger errors from other places.

@gigitux
Copy link
Contributor Author

gigitux commented Dec 2, 2024

In your case, you cannot for example hydrate the form with existing errors if needed (if it already has values), since you need to run validate on touch/blur. There's also no way to mark a field invalid from outside the validation function (which has its runtime controlled by the dataform), this means you can't trigger errors from other places.

You can do it! useForm returns some functions that can be used by developers for advanced uses-cases. For example, you can use setErrors to set an error when the backend returns an error.

async function onCreate(patternTitle, sync) {
  try {
    setIsSaving(true);
    const categories = await Promise.all(
      pattern.categoryTerms.map((termName) => findOrCreateTerm(termName))
    );

    const newPattern = await createPattern(
      patternTitle,
      sync,
      typeof content === "function" ? content() : content,
      categories
    );
    onSuccess({
      pattern: newPattern,
      categoryId: PATTERN_DEFAULT_CATEGORY,
    });
  } catch (error) {
    createErrorNotice(error.message, {
      type: "snackbar",
      id: "pattern-create",
    });
    form.setErrors("title", "Backend Error: This");
  }
}
Screen.Capture.on.2024-12-02.at.12-34-41.mp4

There's also no way to mark a field invalid from outside the validation function (which has its runtime controlled by the dataform), this means you can't trigger errors from other places.

We can do it, I didn't implement these kinds of utils mostly because it is just a POC.

@gigitux gigitux changed the base branch from add/use-data-form-pattern-creation to trunk December 4, 2024 11:44
@gigitux gigitux changed the base branch from trunk to add/use-data-form-pattern-creation December 4, 2024 11:44
value.length >= schema.maxLength ? 'Length is too long' : undefined;
}

return callbacks;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is just a POC

Copy link
Contributor

Choose a reason for hiding this comment

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

This should be part of each "Field type" somehow, each field type has its own "config/constraints"

Copy link
Contributor Author

@gigitux gigitux Dec 4, 2024

Choose a reason for hiding this comment

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

Still not ready for another review! I'm cleaning up the PR and writing another message to explain last changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be part of each "Field type" somehow, each field type has its own "config/constraints"

I'm not sure that I understood this comment, but each field will have a `validationSchema" property:

/**
* Validation schema for the field.
*/
validationSchema?: {
minLength: number;
maxLength: number;
onTouched: boolean;
};

This "schema" will be parsed internally and transform this rules in callbacks that will be invoked to run the validation.

In the next hour, I will write a message to explain the changes that I did (I will tag you)

Copy link
Contributor

Choose a reason for hiding this comment

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

We have field types "number", "text" ... and each field type should define a validation logic for it.

  • number should support "min"/"max" constraints".
  • string should support "minLenght"/"maxLength".
  • isRequired is a constraint that works for all field types.

What I'm saying is that I feel this logic should be provided by the field types and not some global function like this.

@gigitux
Copy link
Contributor Author

gigitux commented Dec 4, 2024

I collect your feedback and try to address in this PR.

Validation schema on the field level

The validation schema is at the field level. The current shape of the validationSchema is a POC, but the idea is that we will use adopt the same structure used by #67324.

Consumers can decide when to show the message error via the onTouched value.

useValidation hook

By default, validation works smoothly with the validationSchema, but consumers might need more control over the form validation state. To address this, the useValidation hook can now be used, allowing consumers to pass a validation object to the dataform component.

This enhancement enables consumers to:

  • Manually set errors (e.g., to invalidate a field after a backend request).
  • Check the form’s validity status
  • Monitor touched fields.

Further utilities are expected to be added to this hook in the future.

cc @youknowriad

@gigitux gigitux changed the base branch from add/use-data-form-pattern-creation to trunk December 4, 2024 13:19
@gigitux gigitux changed the base branch from trunk to add/use-data-form-pattern-creation December 4, 2024 13:19
@@ -47,6 +49,34 @@ export function DataFormLayout< Item >( {
[ form ]
);

const firstValidationRunRef = useRef( false );

useEffect( () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to trigger validation in an effect and not as an event handler?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This runs the validation when the component is mounted. In the case of the pattern modal, we want to disable the button if the form isn't valid:

Screen.Capture.on.2024-12-05.at.11-28-03.mp4

The validation runs on the onChange event too:

onChange={ ( value ) => {
onChange( value );
if (
! validation.touchedFields?.includes(
formField.id
)
) {
validation.setTouchedFields( [
...validation.touchedFields,
formField.id,
] );
}
// Run validation callbacks
const errors = Object.entries(
fieldDefinition?.validationCallbacks ?? {}
).reduce( ( acc, [ key, callback ] ) => {
const error = callback( value[ formField.id ] );
if ( ! error ) {
return acc;
}
return {
...acc,
[ key ]: error,
};
}, {} );
validation.setErrors( formField.id, errors );
} }

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we maybe also move this logic and trigger it in the DataForm component maybe?
The DataFormLayout is also being used within the panel layout, meaning every time the dropdown is opened this logic gets run again.

? Object.values(
validation.errorMessages[ formField.id ] ?? []
)[ 0 ]
: '';
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this doing and is it the right place for this logic?

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 errorMessages dictionary contains an array of error messages for each field ID. This allows a field to have multiple error messages, such as those for password requirements.

Currently, only the first error message is passed to the field.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would also lean towards moving this logic to another place, could this logic be handled within the DataFormProvider or the useValidation hook?

Also I would write this out to an if statement, this is rather difficult to read.

};
}, {} );

validation.setErrors( formField.id, errors );
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need two callbacks for onChange and setErrors. There's something that's bothering me here:

The questions are:

  • Should the validation be performed by DataForm or by the consumer of DataForm in the onChange event handler? (I don't know)
  • If the former, I think we should have errors as an extra argument of the onChange handler maybe
  • It might be better to start with the latter though (validation performed on demand)

I feel like we need a good story here of what we want to achieve, it's still not clear to me.

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 think that the validation should be performed by DataForm, but allow consumers to run their custom logic in their codebase and set errors via the validation object returned by useValidation. Although the extensibility API is still being defined, one of our goals is to allow PHP developers to add or remove fields using only PHP code (from what I understood). If validation is implemented on the consumer side, it will complicate the process for them.

Copy link
Contributor

Choose a reason for hiding this comment

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

It might be better to start with the latter though (validation performed on demand)

Thinking about this more, I would lean towards this. As I am also not entirely settled with the fact that onChange is run first and than the validation/touched states are updated.
While this may work for our current use case, I could see consumers not wanting to update the specific state if there was errors. With this they have no way of knowing.

Passing errors as the additional argument for onChange would fix this.

Copy link
Contributor Author

@gigitux gigitux Dec 10, 2024

Choose a reason for hiding this comment

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

Passing errors as the additional argument for onChange would fix this.

I'm going to work on another PR exploring this approach.

Just to clarify, are you imaging an API like this?


<DataForm
  data={pattern}
  fields={fields}
  form={form}
  onChange={(newData, errors) => {
    const newErros = validateForm(newData, fields);
    setPattern({
      ...pattern,
      ...newData,
    });
    errors.setErrors(newErrors);
  }}
/>
<Button disabled={!isValidForm}>Save</Button>

Few thoughts:

  • On-demand validation prevents marking the form as invalid on the first render. For instance, a form with required fields should be initially invalid, while a form without required fields could be valid from the start.
  • Shifting validation solely to the consumer side means we can't ensure that a field with some constraints defined in the field definition will always undergo validation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just to clarify, are you imaging an API like this?

Thanks for the example, I was actually thinking of keeping the validation triggered within the DataForm component, but that the onChange callback includes the errors ( similar to yours above ):

onChange={(newData, newErrors) => {
    setPattern({
      ...pattern,
      ...newData,
    });
    errors.setErrors(newErrors);
  }}

validationSchema?: {
minLength: number;
maxLength: number;
onTouched: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

what is this boolean about? (it feels misnamed)

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 naming could be improved. Essentially, this boolean indicates whether the error message should be displayed only when the field is dirty or also on the first validation run.

onTouched set to false onTouched set to true
Screen.Capture.on.2024-12-05.at.11-20-34.mp4
Screen.Capture.on.2024-12-05.at.11-21-30.mov

/**
* Validation schema for the field.
*/
validationSchema?: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this top level thing? It seems this is moving us away from JSON schema "format" where the field name, id, type... are mixed with the specific constraints like "minLength"... so maybe we can just move the constraints top level (but make them dependent on the field type for most of them)

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 don’t have a strong preference on this 👍. However, there may be some options that don’t belong in the JSON schema, such as the onTouched property.

Copy link
Contributor

@louwie17 louwie17 left a comment

Choose a reason for hiding this comment

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

Thanks for working on this initial prototype @gigitux, and sorry for the later review. I do like where this is heading, but left some additional suggestions/comments within the code.

if ( firstValidationRunRef.current ) {
return;
}
fieldDefinitions.forEach( ( fieldDefinition ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason you decided to run the validation against all the fieldDefinitions versus the normalizedFormFields ( the fields being rendered ).

This probably overlaps a 100% most of the cases, but for our bulk editing for example, not all the fields get rendered, but the same field definitions are still being passed in. Meaning it would still run validation for the hidden fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting point. I think that you're right! It doesn't make sense to run validation for all fields 👍

@@ -47,6 +49,34 @@ export function DataFormLayout< Item >( {
[ form ]
);

const firstValidationRunRef = useRef( false );

useEffect( () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we maybe also move this logic and trigger it in the DataForm component maybe?
The DataFormLayout is also being used within the panel layout, meaning every time the dropdown is opened this logic gets run again.

? Object.values(
validation.errorMessages[ formField.id ] ?? []
)[ 0 ]
: '';
Copy link
Contributor

Choose a reason for hiding this comment

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

I would also lean towards moving this logic to another place, could this logic be handled within the DataFormProvider or the useValidation hook?

Also I would write this out to an if statement, this is rather difficult to read.

};
}, {} );

validation.setErrors( formField.id, errors );
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be better to start with the latter though (validation performed on demand)

Thinking about this more, I would lean towards this. As I am also not entirely settled with the fact that onChange is run first and than the validation/touched states are updated.
While this may work for our current use case, I could see consumers not wanting to update the specific state if there was errors. With this they have no way of knowing.

Passing errors as the additional argument for onChange would fix this.

onChange={ onChange }
errorMessage={ errorMessage }
onChange={ ( value ) => {
onChange( value );
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we move the logic below to a helper function?
Also does this still work within a Panel layout given we pass in children there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could we maybe also move this logic and trigger it in the DataForm component maybe?
The DataFormLayout is also being used within the panel layout, meaning every time the dropdown is opened this logic gets run again.

Good point, you're right! I will double-check 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.

While this may work for our current use case, I could see consumers not wanting to update the specific state if there was errors. With this they have no way of knowing.

Just to clarify, for instance, you're referring to a field that can render max 5 characters in the input, right? If yes, I think that kind of “constraints” is a UI thing, and it should be handled on the field level.
I think that validation should run on the last “snapshot” of the form values.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just to clarify, for instance, you're referring to a field that can render max 5 characters in the input, right?

Yeah, or an email/link field. But you're right, it should be a UI thing. And thinking about it more, it would be kinda strange if someone triggered an update with the last "valid" state, although a user kept typing in an invalid state.

validationSchema: {
minLength: 1,
maxLength: 10,
onTouched: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

In relation to @youknowriad comment above, it feels a bit weird to me to add these constraints as part of the field definition. Especially if we may use some of these same definitions across post types. For example would title validation be the same across a pattern, template, or category?

I did prefer to have something like validationSupports for a specific field definition or field type ( if needed, it may be irrelevant unless we make use of some of the native validation ).

I would lean towards something similar to the JSON schema, where we link the validation conditions to the key. In this case we may pass something like this to the useValidation hook:

{
  "title": {
            "minLength": 1,
            "maxLength": 10,
            "onTouched": true
        }
}

The only questionable one being onTouched, that may be something better suited for the field definition.

Copy link
Contributor

Choose a reason for hiding this comment

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

Foe me maxLength and minLength are not just validation constraints, they are tools to understand the field itself, the UI to edit it... They should be part of the field definition.

Copy link
Contributor

Choose a reason for hiding this comment

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

@gigitux just pointed out this previous iteration and conversation, where the above approach was suggested, my bad 🙈

Foe me maxLength and minLength are not just validation constraints, they are tools to understand the field itself, the UI to edit it... They should be part of the field definition.

Gotcha, yeah I was still coming from the mindset of keeping the validation logic separate and allow the fields to just handle it visually.
But this in theory is more something for the field types, instead of the specific field definitions that are linked to a key anyway.

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] Experimental Experimental feature or API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants