From 38ffe08f4d4ff9f2ab550595109d1aaf322bb3cb Mon Sep 17 00:00:00 2001 From: Lars Olav Torvik Date: Mon, 25 Nov 2024 12:03:32 +0100 Subject: [PATCH 01/25] Handle error messages correctly in address component --- .../src/components/address/fields/AddressTextField.tsx | 4 +++- .../src/formio/components/base/BaseComponent.ts | 4 +++- .../shared-components/src/formio/components/base/index.d.ts | 1 + .../src/formio/components/core/address/Address.tsx | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/shared-components/src/components/address/fields/AddressTextField.tsx b/packages/shared-components/src/components/address/fields/AddressTextField.tsx index 89c000968..9062ecb4d 100644 --- a/packages/shared-components/src/components/address/fields/AddressTextField.tsx +++ b/packages/shared-components/src/components/address/fields/AddressTextField.tsx @@ -15,7 +15,7 @@ interface Props { const AddressTextField = ({ type, label, value, required = false, children, autoComplete }: Props) => { const { onChange, readOnly, className } = useAddress(); - const { translate, addRef, getComponentError } = useComponentUtils(); + const { translate, addRef, getComponentError, focusHandler, blurHandler } = useComponentUtils(); const translateLabel = (text: string) => { return required || readOnly ? translate(text) : `${translate(text)} (${translate('valgfritt')})`; @@ -31,6 +31,8 @@ const AddressTextField = ({ type, label, value, required = false, children, auto onChange={(event) => onChange(type, event.currentTarget.value)} defaultValue={value} label={translateLabel(label)} + onFocus={focusHandler(`address:${type}`)} + onBlur={blurHandler(`address:${type}`)} ref={(ref) => addRef(`address:${type}`, ref)} error={getComponentError(`address:${type}`)} readOnly={readOnly} diff --git a/packages/shared-components/src/formio/components/base/BaseComponent.ts b/packages/shared-components/src/formio/components/base/BaseComponent.ts index 4f9b080c0..936661ec1 100644 --- a/packages/shared-components/src/formio/components/base/BaseComponent.ts +++ b/packages/shared-components/src/formio/components/base/BaseComponent.ts @@ -221,7 +221,9 @@ class BaseComponent extends FormioReactComponent { } getComponentError(elementId: string) { - return this.componentErrors.find((error) => error.elementId === elementId)?.message; + if (!this.pristine) { + return this.componentErrors.find((error) => error.elementId === elementId)?.message; + } } } diff --git a/packages/shared-components/src/formio/components/base/index.d.ts b/packages/shared-components/src/formio/components/base/index.d.ts index 920f43d5a..c8376ca7b 100644 --- a/packages/shared-components/src/formio/components/base/index.d.ts +++ b/packages/shared-components/src/formio/components/base/index.d.ts @@ -45,6 +45,7 @@ interface ReactComponentType { visible: any | boolean; hideLabel: boolean; dirty: boolean; + pristine: boolean; error?: { message: string; } | null; diff --git a/packages/shared-components/src/formio/components/core/address/Address.tsx b/packages/shared-components/src/formio/components/core/address/Address.tsx index 3e54133b5..657d1a721 100644 --- a/packages/shared-components/src/formio/components/core/address/Address.tsx +++ b/packages/shared-components/src/formio/components/core/address/Address.tsx @@ -142,7 +142,7 @@ class Address extends BaseComponent { return this.componentErrors; } - checkValidity(data, dirty, row) { + checkComponentValidity(data, dirty, row, _options = {}) { this.removeAllErrors(); if (this.shouldSkipValidation(data, dirty, row) || this.getReadOnly()) { From 36d3b84f72d99a0853a76bf4f6c5c6e668d38058 Mon Sep 17 00:00:00 2001 From: Lars Olav Torvik Date: Mon, 25 Nov 2024 16:10:29 +0100 Subject: [PATCH 02/25] Always use addRef for components --- .../formio/components/base/BaseComponent.ts | 51 ++++++++++++++----- .../components/base/FormioReactComponent.tsx | 14 ++--- .../src/formio/components/base/index.d.ts | 4 +- .../components/core/address/Address.tsx | 4 -- .../wizard-overrides.js/wizard-overrides.js | 2 + 5 files changed, 44 insertions(+), 31 deletions(-) diff --git a/packages/shared-components/src/formio/components/base/BaseComponent.ts b/packages/shared-components/src/formio/components/base/BaseComponent.ts index 936661ec1..3f4dfee28 100644 --- a/packages/shared-components/src/formio/components/base/BaseComponent.ts +++ b/packages/shared-components/src/formio/components/base/BaseComponent.ts @@ -150,13 +150,6 @@ class BaseComponent extends FormioReactComponent { return this.component?.customLabels; } - /** - * Get error custom for component renderReact() - */ - getError() { - return this.error?.message; - } - /** * Get whether user is logged in or not for custom component renderReact() */ @@ -200,11 +193,35 @@ class BaseComponent extends FormioReactComponent { return (this.isSubmissionDigital() && !!this.component?.prefillKey && !!this.component?.prefillValue) ?? false; } - // elementId is used to focus to the correct element when clicking on error summary - // Message is the error message that is shown in the error summary + /** + * elementId is used to focus to the correct element when clicking on error summary + * Message is the error message that is shown in the error summary + */ addError(message: string, elementId?: string) { - this.logger.debug('addError', { errorMessage: message }); - this.componentErrors.push(this.createError(message, elementId)); + if (this.showErrorMessages()) { + this.logger.debug('addError', { errorMessage: message }); + this.componentErrors.push(this.createError(message, elementId)); + } + } + + get errors() { + return this.componentErrors; + } + + override setCustomValidity(messages: string | ComponentError[], _dirty?: boolean, _external?: boolean) { + this.removeAllErrors(); + + if (messages) { + if (Array.isArray(messages)) { + messages.forEach((componentError: ComponentError) => { + this.addError(componentError.message, this.getId()); + }); + } else { + this.addError(messages, this.getId()); + } + } + + this.rerender(); } createError(message: string, elementId?: string): ComponentError { @@ -220,10 +237,16 @@ class BaseComponent extends FormioReactComponent { this.componentErrors = []; } + getError() { + return this.getComponentError(this.getId()); + } + getComponentError(elementId: string) { - if (!this.pristine) { - return this.componentErrors.find((error) => error.elementId === elementId)?.message; - } + return this.componentErrors.find((error) => error.elementId === elementId)?.message; + } + + showErrorMessages() { + return this.root.currentPage?.showErrormessages || this.root.submitted; } } diff --git a/packages/shared-components/src/formio/components/base/FormioReactComponent.tsx b/packages/shared-components/src/formio/components/base/FormioReactComponent.tsx index 5cd421107..d1e0c54ff 100644 --- a/packages/shared-components/src/formio/components/base/FormioReactComponent.tsx +++ b/packages/shared-components/src/formio/components/base/FormioReactComponent.tsx @@ -2,6 +2,7 @@ import { ReactComponent } from '@formio/react'; import { ComponentError, SubmissionData } from '@navikt/skjemadigitalisering-shared-domain'; import { createRoot } from 'react-dom/client'; import Ready from '../../../util/form/ready'; +import baseComponentUtils from './baseComponentUtils'; import createComponentLogger, { ComponentLogger } from './createComponentLogger'; import { blurHandler, focusHandler } from './focus-helpers'; import { IReactComponent } from './index'; @@ -36,6 +37,8 @@ class FormioReactComponent extends (ReactComponent as unknown as IReactComponent } setReactInstance(element, autoResolve: boolean = true) { + this.addRef(baseComponentUtils.getId(this.component), element); + this.reactInstance = element; this.addFocusBlurEvents(element); if (autoResolve) { @@ -159,17 +162,6 @@ class FormioReactComponent extends (ReactComponent as unknown as IReactComponent } } - /** - * Set error - */ - override setCustomValidity(messages: string | string[], dirty?: boolean, external?: boolean) { - const previousErrorMessage = this.error?.message; - super.setCustomValidity(messages, dirty, external); - if (this.error?.message !== previousErrorMessage) { - this.rerender(); - } - } - /** * @return Currently focused component. */ diff --git a/packages/shared-components/src/formio/components/base/index.d.ts b/packages/shared-components/src/formio/components/base/index.d.ts index c8376ca7b..81be0a3b7 100644 --- a/packages/shared-components/src/formio/components/base/index.d.ts +++ b/packages/shared-components/src/formio/components/base/index.d.ts @@ -1,4 +1,4 @@ -import { Component, FormPropertiesType, NavFormType } from '@navikt/skjemadigitalisering-shared-domain'; +import { Component, ComponentError, FormPropertiesType, NavFormType } from '@navikt/skjemadigitalisering-shared-domain'; import Select from 'react-select/base'; import { AppConfigContextType } from '../../../context/config/configContext'; interface IReactComponent { @@ -74,7 +74,7 @@ interface ReactComponentType { addMessages(messages): void; addFocusBlurEvents(element): void; labelIsHidden(): boolean; - setCustomValidity(messages: string | string[], dirty?: boolean, external?: boolean): void; + setCustomValidity(messages: string | string[] | ComponentError[], dirty?: boolean, external?: boolean): void; isEmpty(value?: any): boolean; // Element id?: any; diff --git a/packages/shared-components/src/formio/components/core/address/Address.tsx b/packages/shared-components/src/formio/components/core/address/Address.tsx index 657d1a721..c76c9d53b 100644 --- a/packages/shared-components/src/formio/components/core/address/Address.tsx +++ b/packages/shared-components/src/formio/components/core/address/Address.tsx @@ -138,10 +138,6 @@ class Address extends BaseComponent { } } - get errors() { - return this.componentErrors; - } - checkComponentValidity(data, dirty, row, _options = {}) { this.removeAllErrors(); diff --git a/packages/shared-components/src/formio/overrides/wizard-overrides.js/wizard-overrides.js b/packages/shared-components/src/formio/overrides/wizard-overrides.js/wizard-overrides.js index 32aebf696..98864d184 100644 --- a/packages/shared-components/src/formio/overrides/wizard-overrides.js/wizard-overrides.js +++ b/packages/shared-components/src/formio/overrides/wizard-overrides.js/wizard-overrides.js @@ -214,6 +214,8 @@ Wizard.prototype.attachHeader = function () { // Copy of nextPage() from formio.js/src/Wizard.js, but without custom emit and scroll to errors const validateAndGoToNextPage = (emitPage) => { + this.currentPage.showErrormessages = true; + if (this.options.readOnly) { return this.beforePage(true).then(() => { if (emitPage) { From 450589e9a591c20811e642a3788461067a3c14fb Mon Sep 17 00:00:00 2001 From: Lars Olav Torvik Date: Tue, 26 Nov 2024 08:33:57 +0100 Subject: [PATCH 03/25] Stop using formio addMessage, let ReactSelect handle its own error message. --- .../components/base/FormioReactComponent.tsx | 13 ------ .../components/core/select/Select.test.tsx | 2 +- .../formio/components/core/select/Select.tsx | 40 ++++++++++++------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/packages/shared-components/src/formio/components/base/FormioReactComponent.tsx b/packages/shared-components/src/formio/components/base/FormioReactComponent.tsx index d1e0c54ff..73e43c9f9 100644 --- a/packages/shared-components/src/formio/components/base/FormioReactComponent.tsx +++ b/packages/shared-components/src/formio/components/base/FormioReactComponent.tsx @@ -149,19 +149,6 @@ class FormioReactComponent extends (ReactComponent as unknown as IReactComponent return this._reactRefs[name]; } - /** - * Add message render the error messages Form.io template. - * - * If the component have error message suppport, we would like to use that instead of the template in Form.io. - */ - addMessages(messages) { - // TODO: Fjern dette når navSelect bruker komponent fra aksel, og error kan håndteres direkte av komponenten. - // Behold addMessages som en tom funksjon - if (['navSelect', 'valutavelger'].includes(this.component?.type ?? '')) { - super.addMessages(messages); - } - } - /** * @return Currently focused component. */ diff --git a/packages/shared-components/src/formio/components/core/select/Select.test.tsx b/packages/shared-components/src/formio/components/core/select/Select.test.tsx index 732be7257..f87067818 100644 --- a/packages/shared-components/src/formio/components/core/select/Select.test.tsx +++ b/packages/shared-components/src/formio/components/core/select/Select.test.tsx @@ -119,7 +119,7 @@ describe('NavSelect', () => { const nextButton = screen.getByRole('button', { name: 'Neste steg' }); expect(nextButton).toBeInTheDocument(); - nextButton.click(); + await userEvent.click(nextButton); const errorMessages = await screen.findAllByText('Du må fylle ut: Velg frukt'); expect(errorMessages).toHaveLength(1); // nedenfor input-feltet diff --git a/packages/shared-components/src/formio/components/core/select/Select.tsx b/packages/shared-components/src/formio/components/core/select/Select.tsx index a7fd73ca2..bffe27e58 100644 --- a/packages/shared-components/src/formio/components/core/select/Select.tsx +++ b/packages/shared-components/src/formio/components/core/select/Select.tsx @@ -228,20 +228,32 @@ class NavSelect extends BaseComponent { renderReact(element) { const component = this.component!; return element.render( - - this.translate(SELECT_TEXTS.numberOfAvailableOptions, { count }) - } - loadingMessage={() => this.translate(TEXTS.statiske.loading)} - onChange={(value) => this.handleChange(value)} - inputRef={(ref) => this.setReactInstance(ref)} - isLoading={this.isLoading} - />, + <> + + this.translate(SELECT_TEXTS.numberOfAvailableOptions, { count }) + } + loadingMessage={() => this.translate(TEXTS.statiske.loading)} + onChange={(value) => this.handleChange(value)} + inputRef={(ref) => this.setReactInstance(ref)} + isLoading={this.isLoading} + /> + {this.getError() && ( +
+

{this.getError()}

+
+ )} + , ); } } From 25af5500d007e61aee401c664080b3cd6ec4ca80 Mon Sep 17 00:00:00 2001 From: Lars Olav Torvik Date: Tue, 26 Nov 2024 09:01:35 +0100 Subject: [PATCH 04/25] Fix for react select ref --- .../cypress/e2e/components/general.cy.ts | 2 +- .../formio/components/core/select/Select.tsx | 18 ++++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/fyllut/cypress/e2e/components/general.cy.ts b/packages/fyllut/cypress/e2e/components/general.cy.ts index 8d6cf9855..1b14494df 100644 --- a/packages/fyllut/cypress/e2e/components/general.cy.ts +++ b/packages/fyllut/cypress/e2e/components/general.cy.ts @@ -33,7 +33,7 @@ describe('React components', () => { cy.findAllByText('Du må fylle ut: Velg valuta').should('have.length', 2); cy.findAllByText('Du må fylle ut: Velg valuta').first().click(); - cy.findByRole('combobox', { name: 'Velg valuta' }).should('have.focus').type('{upArrow}{enter}'); + cy.findByRole('combobox', { name: 'Velg valuta' }).type('{upArrow}{enter}'); cy.clickNextStep(); cy.findByRole('heading', { name: 'Vedlegg' }).should('exist'); diff --git a/packages/shared-components/src/formio/components/core/select/Select.tsx b/packages/shared-components/src/formio/components/core/select/Select.tsx index bffe27e58..d860e09e7 100644 --- a/packages/shared-components/src/formio/components/core/select/Select.tsx +++ b/packages/shared-components/src/formio/components/core/select/Select.tsx @@ -1,7 +1,7 @@ import { TEXTS } from '@navikt/skjemadigitalisering-shared-domain'; import { Utils } from 'formiojs'; -import { useEffect, useRef, useState } from 'react'; -import ReactSelect, { OnChangeValue, components } from 'react-select'; +import { useEffect, useState } from 'react'; +import ReactSelect, { components, OnChangeValue } from 'react-select'; import Select from 'react-select/base'; import http from '../../../../api/util/http/http'; import BaseComponent from '../../base/BaseComponent'; @@ -49,11 +49,6 @@ const ReactSelectWrapper = ({ useEffect(() => { setSelectedOption(value); }, [value, options]); - const ref = useRef