Skip to content

CHI-3857: Hide validation errors until submit attempted#4272

Merged
stephenhand merged 6 commits intomasterfrom
CHI-3857-fix_premature_validation
May 1, 2026
Merged

CHI-3857: Hide validation errors until submit attempted#4272
stephenhand merged 6 commits intomasterfrom
CHI-3857-fix_premature_validation

Conversation

@stephenhand
Copy link
Copy Markdown
Collaborator

@stephenhand stephenhand commented Apr 30, 2026

Description

Pre engagement form was showing validation errors too early. Now only shows them after a submit is attempted or after that specific field has been touched

Checklist

  • Corresponding issue has been opened
  • New tests added
  • Feature flags added
  • Strings are localized
  • Tested for chat contacts
  • Tested for call contacts

Other Related Issues

None

Verification steps

AFTER YOU MERGE

  1. Cut a release tag using the Github workflow. Wait for it to complete and notify in the #aselo-deploys Slack channel.
  2. Comment on the ticket with the release tag version AND any additional instructions required to configure an environment to test the changes.
  3. Only then move the ticket into the QA column in JIRA

You are responsible for ensuring the above steps are completed. If you move a ticket into QA without advising what version to test, the QA team will assume the latest tag has the changes. If it does not, the following confusion is on you! :-P

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the Pre-Engagement form UX so validation errors (inline messages + error styling) are hidden until the user attempts to submit the form.

Changes:

  • Adds a showError flag plumbed through generateForm → individual form input components.
  • Gates hasError styling and error message rendering in Input/Select/DependentSelect/Checkbox based on showError.
  • Tracks wasSubmitAttempted in PreEngagementFormPhase and passes it as showError.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
aselo-webchat-react-app/src/components/forms/formInputs/index.tsx Threads new showError option through form generation.
aselo-webchat-react-app/src/components/forms/formInputs/Input.tsx Hides input error styling/message until showError is true.
aselo-webchat-react-app/src/components/forms/formInputs/Select.tsx Hides select error styling/message until showError is true.
aselo-webchat-react-app/src/components/forms/formInputs/DependentSelect.tsx Hides dependent select error styling/message until showError is true.
aselo-webchat-react-app/src/components/forms/formInputs/Checkbox.tsx Hides checkbox error styling/message until showError is true.
aselo-webchat-react-app/src/components/PreEngagementFormPhase.tsx Introduces wasSubmitAttempted state and passes it into generateForm.

Comment thread aselo-webchat-react-app/src/components/PreEngagementFormPhase.tsx
Comment on lines +107 to +110
getItem,
setItemValue,
showError: wasSubmitAttempted,
})}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

This change alters when validation errors are displayed (hidden until a submit attempt). Please add/adjust tests to cover the new UI behavior (e.g., errors not rendered / hasError not set before submit, then shown after submit attempt), since existing tests mainly assert submission side effects rather than error visibility.

Copilot uses AI. Check for mistakes.
Comment on lines 24 to 31
const generateFormItem = ({
definition,
defaultValue = '',
handleChange,
getItem,
setItemValue,
showError,
}: {
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

defaultValue is computed in generateForm and destructured in generateFormItem, but it isn't actually passed to any of the form input components here. Either forward it to the relevant inputs (e.g., Select/Checkbox) or remove the unused defaultValue plumbing (including getDefaultValue) to avoid misleading dead code.

Copilot uses AI. Check for mistakes.
definition: PreEngagementFormItem;
defaultValue?: string | boolean;
handleChange: (payload: { name: string; value: string | boolean }) => void;
getItem: (inptuName: string) => PreEngagementDataItem;
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

Typo in the getItem callback type: parameter name is inptuName (missing 'u' in 'input'). Renaming to inputName will improve readability and avoid propagating the typo to other signatures.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread aselo-webchat-react-app/src/components/PreEngagementFormPhase.tsx Outdated
Comment on lines +76 to +77
const setItemValue = ({ name }: { name: string }) => {
setFieldsTouched(fieldsTouched.add(name));
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

setItemValue now ignores the value in the { name, value } payload. This breaks callers that rely on programmatic value updates (e.g., DependentSelect resets its child select via setItemValue({ name, value }) when the parent changes). Consider handling the passed value by dispatching an updatePreEngagementDataField(s) update for that specific field (or updating the DOM element before syncing) so dependent resets actually take effect.

Suggested change
const setItemValue = ({ name }: { name: string }) => {
setFieldsTouched(fieldsTouched.add(name));
const setItemValue = ({ name, value }: { name: string; value?: any }) => {
setFieldsTouched(previousFieldsTouched => {
const nextFieldsTouched = new Set(previousFieldsTouched);
nextFieldsTouched.add(name);
return nextFieldsTouched;
});
if (typeof value !== 'undefined') {
dispatch(updatePreEngagementDataFields([{ name, value }]) as any);
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +112
{generateForm({
form: preEngagementFormDefinition.fields,
handleChange,
getItem,
setItemValue,
showError: name => wasSubmitAttempted || fieldsTouched.has(name),
})}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

This PR introduces new UI behavior (hide per-field validation errors until submit is attempted or the field is touched), but there are no tests asserting that errors are hidden initially and shown after touch/submit. Since PreEngagementFormPhase.test.tsx already has extensive validation coverage, please add a focused test (or two) to lock in the new showError gating behavior.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Comment on lines +84 to +86
const setItemValue = ({ name, value }: { name: string; value: string | boolean }) => {
setFieldsTouched(prevFieldsTouched => {
const nextFieldsTouched = new Set(prevFieldsTouched);
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

fieldsTouched is being mutated in-place (fieldsTouched.add(name)) and then passed back into state. Because the Set reference doesn't change, React may bail out and the component won't re-render, so showError may never update for touched fields. Use a functional state update that creates a new Set (e.g., copy prev then add) to preserve immutability and ensure re-rendering.

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +72
let value: string | boolean;
if (updates[field.name]) {
value = updates[field.name];
} else {
value = field.type === FormInputType.Checkbox ? (element as HTMLInputElement).checked : element.value;
}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

setPreEngagementDataFromDom checks if (updates[field.name]), which ignores valid overrides like '' (empty string) or false (checkbox), and it will also skip overrides when the updated value is intentionally falsy. Check for key presence (e.g., field.name in updates / hasOwnProperty) or updates[field.name] !== undefined instead.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Already fixed in d8268ccif (updates[field.name]) was changed to if (field.name in updates) so falsy overrides like '' and false are correctly applied.

Comment on lines +84 to +87
const setItemValue = ({ name, value }: { name: string; value: string | boolean }) => {
setFieldsTouched(prevFieldsTouched => {
const nextFieldsTouched = new Set(prevFieldsTouched);
nextFieldsTouched.add(name);
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

setPreEngagementDataFromDom({ name, value }) passes an object with keys literally named name and value, so updates[field.name] will never match the field being updated (unless a field is actually named "name"/"value"). If you intend to override a specific field, pass a record keyed by the field name (computed property) so the override is applied.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in d8268cc. Two bugs addressed:

  1. setItemValue now passes { [name]: value } (computed property key) so the override object is keyed by the actual field name (e.g. { friendlyName: 'John' }) rather than the literal { name: 'friendlyName', value: 'John' }.
  2. setPreEngagementDataFromDom now checks field.name in updates instead of if (updates[field.name]) so falsy overrides like '' (cleared input) or false (unchecked checkbox) are correctly applied rather than falling back to the DOM value.

Comment on lines +118 to +120
{generateForm({
form: preEngagementFormDefinition.fields,
handleChange,
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

PR title/description says validation errors should only show after a submit attempt, but showError also enables errors as soon as a field is touched (wasSubmitAttempted || fieldsTouched.has(name)). If the intended behavior is strictly "after submit", drop the fieldsTouched condition (or adjust the PR description).

Copilot uses AI. Check for mistakes.
Comment on lines 114 to +120
<Text {...titleStyles} as="h3">
<LocalizedTemplate code={titleText} />
</Text>
<Box {...fieldStyles}>
{generateForm({ form: preEngagementFormDefinition.fields, handleChange, getItem, setItemValue })}
{generateForm({
form: preEngagementFormDefinition.fields,
handleChange,
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

This change introduces new UI behavior (gating error rendering via showError), but there are no tests asserting that validation errors are hidden before the first submit attempt and become visible after submit. Please add/adjust tests in PreEngagementFormPhase.test.tsx to cover the new error-display timing so regressions are caught.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Added three focused tests in commit 778e42c:

  1. Errors hidden before submit — pre-loads Redux with a RequiredFieldError state and asserts the error span is not rendered before any interaction.
  2. Errors shown after submit — submits the form with a required field empty and asserts the error span appears afterwards.
  3. Error shown after field is touched — blurs an empty required field (without submitting) and asserts the error span appears for that field only.

…romDom overrides

Agent-Logs-Url: https://github.com/techmatters/flex-plugins/sessions/9fbd9699-ac66-4fff-84a9-7f6e62dbaee6

Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com>
@stephenhand stephenhand merged commit d2ff65b into master May 1, 2026
26 checks passed
@stephenhand stephenhand deleted the CHI-3857-fix_premature_validation branch May 1, 2026 09:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants