Before you get started, it’s helpful to know how to work with Array Data. For more information, see:

If you want to add a contact info page that shows the Veteran's contact info and allows updating the changes to their profile directly, then follow this example. Clicking edit for any contact info item takes the Veteran to a new page, then returns them to the contact info page once complete.

For more information on why the Forms Library Team recommends this configuration, please see the last section of the document, Previous implementations of contact info Array field (aka list loop).

Flowchart showing six boxes, four of which are contained in one larger box, Contact list loop. The small box on the left, Previous page, points with a solid arrow to the next small box, Contact Info, the first within the large box. Contact Info and the three small boxes to its right, point to and from Contact Info with dotted lines between them, including Update address, Update phone, and Update email. Another solid line leads from All Items to outside the large box, to the last small box, Continue form

Setting up the form config

The flow of this method sets the contact info page as the main page, and each edit page as a new path for each entry. Set up the form config by adding the contact info page and 3 hidden edit pages:

chapters: {
  infoPages: {
    title: 'Veteran details',
    reviewDescription: ReviewDescription,
    pages: {
      // ... other pages
      confirmContactInformation: {
        title: 'Contact information',
        path: 'contact-information',
        uiSchema: contactInfo.uiSchema,
        schema: contactInfo.schema,
      editMobilePhone: {
        title: 'Edit mobile phone',
        path: 'edit-mobile-phone',
        CustomPage: EditPhone,
        depends: () => false, // accessed from contact info page
        uiSchema: {},
        schema: { type: 'object', properties: {} },
      editEmailAddress: {
        title: 'Edit email address',
        path: 'edit-email-address',
        CustomPage: EditEmail,
        depends: () => false, // accessed from contact info page
        uiSchema: {},
        schema: { type: 'object', properties: {} },
      editMailingAddress: {
        title: 'Edit mailing address',
        path: 'edit-mailing-address',
        CustomPage: EditAddress,
        depends: () => false, // accessed from contact info page
        uiSchema: {},
        schema: { type: 'object', properties: {} },


  • ReviewDescription will render all the info on the review & submit page, so we don't need to include the CustomPageReview entry in the form.

  • contactInfo details are described below

  • The three edit pages all have:

    • a CustomPage rendering the component without depending on the form system

    • the depends function set to return false

    • The uiSchema set as an empty object since the CustomPage doesn't use it, but the entry is still needed

    • A schema defined; this is required, even though it's not used


Renders the contact info on the review & submit page based on the form data structure, or uses the profile.vapContactInfo data from Redux.

To simplify this component, we opted to add a link to the profile page within the review & submit accordion:

  rel="noopener noreferrer"
  aria-label="Edit contact information in your profile"
  Edit Profile

Contact info page

uiSchema & schema

The uiSchema & schema are relatively straightforward:

export default {
  uiSchema: {
    'ui:title': ' ',
    'ui:description': ContactInfoDescription,
    'ui:required': () => true, // don't allow progressing without all contact info
    'ui:validations': [contactInfoValidation], // needed to block form progression
    'ui:options': {
      hideOnReview: true, // We're using the `ReveiwDescription`, so don't show this page
      forceDivWrapper: true, // It's all info and links, so we don't need a fieldset or legend
  schema: {
    type: 'object',
    properties: {}, // no form elements on this page

ContactInfoDescription component

The ContactInfoDescription is somewhat complex in that it displays alerts for missing info and a success alert once everything has been updated, but we'll just cover the basics here (full contact info component code). We need to render the contact info and links to their edit page:

    <h4 className="vads-u-font-size--h3">Mobile phone number</h4>
    <va-telephone contact={phoneNumber} extension={phoneExt} not-clickable />
      <Link to="/edit-mobile-phone" aria-label="Edit mobile phone number">
    <h4 className="vads-u-font-size--h3">Email address</h4>
    <span>{email?.emailAddress || ''}</span>
      <Link to="/edit-email-address" aria-label="Edit email address">
    <h4 className="vads-u-font-size--h3">Mailing address</h4>
    <AddressView data={mailingAddress} />
      <Link to="/edit-mailing-address" aria-label="Edit mailing address">

CustomPage component

Since all profile entries use the same profile component, we only need to render the wrapper and change the field name:

import React from 'react';

import InitializeVAPServiceID from '@@vap-svc/containers/InitializeVAPServiceID';
import ProfileInformationFieldController from '@@vap-svc/components/ProfileInformationFieldController';
import { FIELD_NAMES } from '@@vap-svc/constants';

const buildPage = ({ title, field, goToPath }) => {
  const handlers = {
    onSubmit: event => {
      // This prevents this nested form submit event from passing to the
      // outer form and causing a page advance
    cancel: () => {
    success: () => {

  return (
    <div className="va-profile-wrapper" onSubmit={handlers.onSubmit}>

export const EditPhone = ({ title, goToPath }) =>
  buildPage({ title, goToPath, field: 'MOBILE_PHONE' });

export const EditEmail = ({ title, goToPath }) =>
  buildPage({ title, goToPath, field: 'EMAIL' });

export const EditAddress = ({ title, goToPath }) =>
  buildPage({ title, goToPath, field: 'MAILING_ADDRESS' });

Previous implementations of contact info Array field (aka list loop)

The VSA Claims & Appeals team had previously tried numerous implementations for this, and they’ve detailed their steps to finding the best method here:

  • Adding prefilled contact info as inline form elements; but once the form was submitted, the intake personnel would see the address discrepancy and update the Veteran's profile. The time between submission and update could be months.

  • Adding a link to the profile page, but this took the Veteran out of the form flow. And if the profile page was opened in a new tab, we'd have to ask the Veteran to reload the form in the original tab before the profile changes could be seen – this took multiple steps:

    • Click on "finish this application later"

    • Reload the form saved page (returns them to the Introduction page)

    • Click on "Continue your application"

  • Used profile's original code to allow editing of contact info inside modal windows. This still wasn't the best user experience.

  • Switched to profile's inline editing of contact info; but if you attempted to edit multiple entries at once, a modal would open stating that an edit was already in progress

  • Once the custom page method was added to the form system, we knew this was the right method! Clicking edit of any contact info item would take the Veteran to a new page, then return them to the contact info page once complete. We're still using the profile inline editing components, but only on the edit page