Skip to content

Commit

Permalink
[open-formulieren/open-forms#5006] Updated addressNL component to man…
Browse files Browse the repository at this point in the history
…ually fill in city and streetname

Until now the addressNL component would retrieve the city and street
name based on the postcode and housenumber. These fields were always
read-only but now we want to be able to fill in city and street name if
something goes wrong with the API call.
  • Loading branch information
vaszig committed Jan 23, 2025
1 parent da9810b commit a9f8193
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 80 deletions.
114 changes: 83 additions & 31 deletions src/formio/components/AddressNL.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import {Formik, useFormikContext} from 'formik';
import debounce from 'lodash/debounce';
import {useContext, useEffect} from 'react';
import {useContext, useEffect, useState} from 'react';
import {createRoot} from 'react-dom/client';
import {Formio} from 'react-formio';
import {FormattedMessage, IntlProvider, defineMessages, useIntl} from 'react-intl';
Expand Down Expand Up @@ -61,6 +61,50 @@ export default class AddressNL extends Field {
if (this.component.validate.plugins && this.component.validate.plugins.length) {
updatedOptions.async = true;
}

if (!dirty) {
return super.checkComponentValidity(data, dirty, row, updatedOptions);

Check warning on line 66 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L66

Added line #L66 was not covered by tests
}

const {postcode, houseNumber, city, streetName} = this.dataValue;

Check warning on line 69 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L69

Added line #L69 was not covered by tests
if (this.component?.validate?.required) {
if (
this.component.deriveAddress &&
[postcode, houseNumber, city, streetName].some(value => value === '')

Check warning on line 73 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L73

Added line #L73 was not covered by tests
) {
const messages = [

Check warning on line 75 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L75

Added line #L75 was not covered by tests
{
message: this.t('Required fields can not be empty.'),
level: 'error',
},
];
this.setComponentValidity(messages, true, false);
return false;

Check warning on line 82 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L81-L82

Added lines #L81 - L82 were not covered by tests
} else if (
!this.component.deriveAddress &&
[postcode, houseNumber].some(value => value === '')

Check warning on line 85 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L85

Added line #L85 was not covered by tests
) {
const messages = [

Check warning on line 87 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L87

Added line #L87 was not covered by tests
{
message: this.t('Required fields can not be empty.'),
level: 'error',
},
];
this.setComponentValidity(messages, true, false);
return false;

Check warning on line 94 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L93-L94

Added lines #L93 - L94 were not covered by tests
}
} else {
if ((postcode && !houseNumber) || (!postcode && houseNumber)) {
const messages = [

Check warning on line 98 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L98

Added line #L98 was not covered by tests
{
message: this.t('Both postcode and housenumber fields are required or none of them.'),
level: 'error',
},
];
this.setComponentValidity(messages, true, false);
return false;

Check warning on line 105 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L104-L105

Added lines #L104 - L105 were not covered by tests
}
}
return super.checkComponentValidity(data, dirty, row, updatedOptions);
}

Expand All @@ -77,6 +121,7 @@ export default class AddressNL extends Field {
city: '',
streetName: '',
secretStreetCity: '',
autoPopulated: false,
};
}

Expand Down Expand Up @@ -199,6 +244,22 @@ const FIELD_LABELS = defineMessages({
description: 'Label for addressNL houseNumber input',
defaultMessage: 'House number',
},
houseLetter: {
description: 'Label for addressNL houseLetter input',
defaultMessage: 'House letter',
},
houseNumberAddition: {
description: 'Label for addressNL houseNumberAddition input',
defaultMessage: 'House number addition',
},
streetName: {
description: 'Label for addressNL streetName input',
defaultMessage: 'Street name',
},
city: {
description: 'Label for addressNL city input',
defaultMessage: 'City',
},
});

const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => {
Expand All @@ -216,6 +277,7 @@ const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => {
});
let postcodeSchema = z.string().regex(postcodeRegex, {message: postcodeErrorMessage});

let streetNameSchema = z.string();

Check warning on line 280 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L280

Added line #L280 was not covered by tests
const {pattern: cityPattern = '', errorMessage: cityErrorMessage = ''} = city;
let citySchema = z.string();
if (cityPattern) {
Expand All @@ -235,12 +297,15 @@ const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => {
if (!required) {
postcodeSchema = postcodeSchema.optional();
houseNumberSchema = houseNumberSchema.optional();
streetNameSchema = streetNameSchema.optional();
citySchema = citySchema.optional();

Check warning on line 301 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L300-L301

Added lines #L300 - L301 were not covered by tests
}

return z
.object({
postcode: postcodeSchema,
city: citySchema.optional(),
streetName: streetNameSchema,
city: citySchema,
houseNumber: houseNumberSchema,
houseLetter: z
.string()
Expand Down Expand Up @@ -340,6 +405,7 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi
postcode: true,
houseNumber: true,
city: true,
streetName: true,
}}
validationSchema={toFormikValidationSchema(
addressNLSchema(required, intl, {
Expand Down Expand Up @@ -368,6 +434,7 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi
const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => {
const {values, isValid, setFieldValue} = useFormikContext();
const {baseUrl} = useContext(ConfigContext);
const [isAddressAutoFilled, setAddressAutoFilled] = useState(true);

Check warning on line 437 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L437

Added line #L437 was not covered by tests
const useColumns = layout === 'doubleColumn';

useEffect(() => {
Expand All @@ -394,6 +461,12 @@ const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => {
setFieldValue('city', data['city']);
setFieldValue('streetName', data['streetName']);
setFieldValue('secretStreetCity', data['secretStreetCity']);

// mark the auto-filled fields as populated and disabled when they have been both
// retrieved from the API and they do have a value
const dataRetrieved = !!(data['city'] && data['streetName']);
setAddressAutoFilled(dataRetrieved);
setFieldValue('autoPopulated', dataRetrieved);

Check warning on line 469 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L468-L469

Added lines #L468 - L469 were not covered by tests
};

return (
Expand All @@ -406,45 +479,24 @@ const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => {
>
<PostCodeField required={required} autoFillAddress={autofillAddress} />
<HouseNumberField required={required} autoFillAddress={autofillAddress} />
<TextField
name="houseLetter"
label={
<FormattedMessage
description="Label for addressNL houseLetter input"
defaultMessage="House letter"
/>
}
/>
<TextField name="houseLetter" label={<FormattedMessage {...FIELD_LABELS.houseLetter} />} />
<TextField
name="houseNumberAddition"
label={
<FormattedMessage
description="Label for addressNL houseNumberAddition input"
defaultMessage="House number addition"
/>
}
label={<FormattedMessage {...FIELD_LABELS.houseNumberAddition} />}
/>
{deriveAddress && (
<>
<TextField
name="streetName"
label={
<FormattedMessage
description="Label for addressNL streetName read only result"
defaultMessage="Street name"
/>
}
disabled
label={<FormattedMessage {...FIELD_LABELS.streetName} />}
disabled={isAddressAutoFilled}
isRequired={required}
/>
<TextField
name="city"
label={
<FormattedMessage
description="Label for addressNL city read only result"
defaultMessage="City"
/>
}
disabled
label={<FormattedMessage {...FIELD_LABELS.city} />}
disabled={isAddressAutoFilled}
isRequired={required}
/>
</>
)}
Expand Down
35 changes: 30 additions & 5 deletions src/formio/components/AddressNL.stories.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {expect, userEvent, waitFor, within} from '@storybook/test';

import {ConfigDecorator, withUtrechtDocument} from 'story-utils/decorators';
import {sleep} from 'utils';

import {
mockBAGDataGet,
Expand Down Expand Up @@ -84,6 +85,28 @@ export const ClientSideValidation = {
},
};

export const Required = {
args: {
extraComponentProperties: {
validate: {
required: true,
},
},
},
render: SingleFormioComponent,
play: async ({canvasElement}) => {
await sleep(500);
const canvas = within(canvasElement);

(await canvas.findByLabelText('Huisnummer')).focus();
await userEvent.tab();

expect(await canvas.findByText('Postcode is verplicht.')).toBeVisible();
expect(await canvas.findByText('Huisnummer is verplicht.')).toBeVisible();
expect(await canvas.findByText('required')).toBeVisible();
},
};

export const NotRequired = {
args: {
extraComponentProperties: {
Expand Down Expand Up @@ -195,7 +218,7 @@ export const WithFailedBRKValidation = {
// },
};

export const WithDeriveCityStreetNameWithData = {
export const WithDeriveCityStreetNameWithDataNotRequired = {
render: SingleFormioComponent,
parameters: {
msw: {
Expand Down Expand Up @@ -234,7 +257,7 @@ export const WithDeriveCityStreetNameWithData = {
const houseNumberInput = await canvas.findByLabelText('Huisnummer');
await userEvent.type(houseNumberInput, '1');

const city = await canvas.findByLabelText('Stad');
const city = await canvas.findByLabelText('Plaats');
const streetName = await canvas.findByLabelText('Straatnaam');

await userEvent.tab();
Expand Down Expand Up @@ -322,7 +345,7 @@ export const WithDeriveCityStreetNameWithDataIncorrectCity = {
const houseNumberInput = await canvas.findByLabelText('Huisnummer');
await userEvent.type(houseNumberInput, '1');

const city = await canvas.findByLabelText('Stad');
const city = await canvas.findByLabelText('Plaats');
const streetName = await canvas.findByLabelText('Straatnaam');

await userEvent.tab();
Expand All @@ -338,7 +361,7 @@ export const WithDeriveCityStreetNameWithDataIncorrectCity = {
},
};

export const WithDeriveCityStreetNameNoData = {
export const WithDeriveCityStreetNameNoDataAndNotRequired = {
render: SingleFormioComponent,
parameters: {
msw: {
Expand All @@ -365,14 +388,16 @@ export const WithDeriveCityStreetNameNoData = {
const houseNumberInput = await canvas.findByLabelText('Huisnummer');
await userEvent.type(houseNumberInput, '1');

const city = await canvas.findByLabelText('Stad');
const city = await canvas.findByLabelText('Plaats');
const streetName = await canvas.findByLabelText('Straatnaam');

await userEvent.tab();

await waitFor(() => {
expect(city.value).toBe('');
expect(city).not.toBeDisabled();
expect(streetName.value).toBe('');
expect(streetName).not.toBeDisabled();
});
},
};
Expand Down
24 changes: 12 additions & 12 deletions src/i18n/compiled/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@
"value": "Check and confirm"
}
],
"AKhmW+": [
{
"type": 0,
"value": "Street name"
}
],
"AM6xqd": [
{
"type": 0,
Expand Down Expand Up @@ -491,12 +497,6 @@
"value": "Remove"
}
],
"DEetjI": [
{
"type": 0,
"value": "Street name"
}
],
"DK2ewv": [
{
"type": 0,
Expand Down Expand Up @@ -1925,12 +1925,6 @@
"value": "."
}
],
"osSl3z": [
{
"type": 0,
"value": "City"
}
],
"ovI+W7": [
{
"type": 0,
Expand Down Expand Up @@ -2069,6 +2063,12 @@
"value": "Send code"
}
],
"s4+4p2": [
{
"type": 0,
"value": "City"
}
],
"sSmY1N": [
{
"type": 0,
Expand Down
24 changes: 12 additions & 12 deletions src/i18n/compiled/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@
"value": "Controleer en bevestig"
}
],
"AKhmW+": [
{
"type": 0,
"value": "Straatnaam"
}
],
"AM6xqd": [
{
"type": 0,
Expand Down Expand Up @@ -491,12 +497,6 @@
"value": "Verwijderen"
}
],
"DEetjI": [
{
"type": 0,
"value": "Straatnaam"
}
],
"DK2ewv": [
{
"type": 0,
Expand Down Expand Up @@ -1929,12 +1929,6 @@
"value": " zijn."
}
],
"osSl3z": [
{
"type": 0,
"value": "Stad"
}
],
"ovI+W7": [
{
"type": 0,
Expand Down Expand Up @@ -2073,6 +2067,12 @@
"value": "Verstuur code"
}
],
"s4+4p2": [
{
"type": 0,
"value": "Plaats"
}
],
"sSmY1N": [
{
"type": 0,
Expand Down
Loading

0 comments on commit a9f8193

Please sign in to comment.