SubTask

Sections

  • What is a sub-task?

  • When would I use a sub-task?

  • How do I create a sub-task?

  • Considerations

What is a sub-task?

In this case, a sub-task is a question, or series of questions, to assess the Veteran's eligibility before starting a form. This was previously done by the form wizard, but the pattern was deprecated.

A new sub-task pattern was created to replace the wizard. Its goal is to ask one question per page, and allow the Veteran to only need to make one decision per page.

Each sub-task branch must include a clear path to either start the form, or describe the next steps to help them continue outside of the form.

When would I use a sub-task?

For this SubTask component, include it when a form needs particular questions answered before starting the form flow; i.e. questions that are asked before showing the introduction page.

In the case of the Supplemental Claim form (20-0995), the phase 1 online form will only support one benefit type. All other types would require the Veteran or claimant to fill out a paper form. The sub-task question is set up to ask the benefit type; if "compensation" is selected and the "continue" button is used, the Veteran is directed to the introduction page, otherwise the next page shown is a message directing the Veteran to download the form. There is also a back button allowing them to return to the benefit type question.

More complex patterns, such as those used for form 526, can also be used.

How do I create a sub-task?

Set up

  • Create a sub-task folder within your app

  • Add a container component which includes:

    • Render the SubTask component (import from platform/forms/sub-task)

    • Form footer (optional)

  • Add sub-task pages

  • Add analytics based on the wizard events

  • In the form routes file, add a /start path and render the container

  • And the App.jsx needs code to redirect the Veteran to the /start page when the needed sub-task data is missing

File structure
.
├─ App
│   ├─ containers
│   │  └─ App.jsx
│   ├─ sub-task
│   │  ├─ pages
│   │  │  ├─ index.js
│   │  │  ├─ other.jsx
│   │  │  ├─ pageNames.js
│   │  │  └─ start.jsx
│   │  └─ Container.jsx
│   └─ routes.js
.
CODE
Sub-task example changes

index.js

Group all the pages:

import start from './start';
import other from './other';

export default [start, other];
JS

other.jsx

Stop page, not allowing Veteran to start the form, but gives clear next steps:

import React from 'react';
import recordEvent from 'platform/monitoring/record-event';

import pageNames from './pageNames';
import DownloadLink from '../../content/DownloadLink';
import { BENEFIT_OFFICES_URL } from '../../constants';

const DecisionReviewPage = () => {
  recordEvent({
    event: 'howToWizard-alert-displayed',
    'reason-for-alert': 'veteran wants to submit an unsupported claim type',
  });

  const handlers = {
    officeLinkClick: () => {
      recordEvent({
        event: 'howToWizard-alert-link-click',
        'howToWizard-alert-link-click-label': 'benefit office',
      });
    },
  };

  return (
    <div id={pageNames.other}>
      <h1 className="vads-u-margin-bottom--0">
        Filing non-disability Supplemental Claims
      </h1>
      <p>
        We don’t support claims other than disability online at this time.
        You’ll need to fill out and submit VA Form 20-0995 by mail or in person.
      </p>
      <a href={BENEFIT_OFFICES_URL} onClick={handlers.officeLinkClick}>
        Send the completed form to the benefit office
      </a>{' '}
      that matches the benefit type you select on the form.
      <p className="vads-u-margin-bottom--0">
        <DownloadLink content="Download VA Form 20-0995" />
      </p>
    </div>
  );
};

export default {
  name: pageNames.other,
  component: DecisionReviewPage,
  next: null, // no continue button
  back: pageNames.start, // return to start page
};
JSX

pageNames.js
export default {
  start: 'start',
  other: 'other',
};
JS

start.jsx
import React from 'react';
import PropTypes from 'prop-types';
import {
  VaRadio,
  VaRadioOption,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';

import recordEvent from 'platform/monitoring/record-event';

import { BASE_URL } from '../../constants';
import pageNames from './pageNames';

const content = {
  groupLabel: 'For what type of claim are you requesting a Supplemental Claim?',
  errorMessage: 'Please choose a benefit type',
};

const options = [
  {
    value: 'compensation',
    label: 'Disability compensation claim',
  },
  {
    value: pageNames.other,
    label: 'Claim other than disability compensation',
  },
];

const optionValues = options.map(option => option.value);

const getNextPage = data =>
  data?.benefitType === optionValues[0]
    ? `${BASE_URL}/introduction` // valid benefit type, go to intro page
    : pageNames.other; // benefit type not supported

const validate = ({ benefitType } = {}) => optionValues.includes(benefitType);

/**
 * Benefit type page
 * @param {Object} data - sub-task data
 * @param {Boolean} error - page submitted & error state
 * @param {Function} setPageData - updates form data, sub-task data &
 *  session storage
 * @returns {JSX}
 */
const BenefitType = ({ data = {}, error, setPageData }) => {
  const handlers = {
    setBenefitType: ({ target }) => {
      const { value } = target;
      setPageData({ benefitType: value || null });

      recordEvent({
        event: 'howToWizard-formChange',
        'form-field-type': 'form-radio-buttons',
        'form-field-label':
          'For what benefit type are you requesting a Supplemental Claim?',
        'form-field-value': value,
      });
    },
  };

  return (
    <>
      <h1 className="vads-u-margin-bottom--0">
        Is Supplemental Claim VA Form 20-0995 what I need?
      </h1>
      <p>
        Use this form if you disagree with our decision on your claim and have
        new and relevant evidence to submit.
      </p>
      <p>Answer a question to get started.</p>
      <VaRadio
        label={content.groupLabel}
        error={error ? content.errorMessage : null}
        onRadioOptionSelected={handlers.setBenefitType}
      >
        {options.map(({ value, label }) => (
          <VaRadioOption
            key={value}
            className="vads-u-margin-y--2"
            name="benefitType"
            id={value}
            value={value}
            label={label}
            checked={value === data.benefitType}
          />
        ))}
      </VaRadio>
    </>
  );
};

BenefitType.propTypes = {
  data: PropTypes.shape({}),
  error: PropTypes.bool,
  setPageData: PropTypes.func,
};

export default {
  name: pageNames.start,
  component: BenefitType,
  validate, // validation check before allowing continue
  back: null, // no back button
  next: getNextPage, // dynamically determine next page
};
JSX

Container.jsx
import React from 'react';

import FormFooter from 'platform/forms/components/FormFooter';
import SubTask from 'platform/forms/sub-task';

import pages from './pages';
import formConfig from '../config/form';

export const SubTaskContainer = () => (
  <article className="row">
    <div className="usa-width-two-thirds medium-8 columns vads-u-margin-bottom--2">
      <SubTask pages={pages} />
    </div>
    <FormFooter formConfig={formConfig} />
  </article>
);

export default SubTaskContainer;
JSX

App & routing

The App can use the sub-task data to determine if the Veteran should be re-directed to the /start page, where the sub-task will live. See the "Data" subsection (under "Considerations") below for more details. Link To Data Section

App.jsx
const formType =
  form.data.type ||
  getStoredSubTask()?.type; // session storage value

if (!formType) {
  router.push('/start');
  return (
    <h1 className="vads-u-font-family--sans vads-u-font-size--base vads-u-font-weight--normal">
      <va-loading-indicator message="Please wait while we start the application." />
    </h1>
  );
}
JSX

Within the routes.js file, add the /start path:

routes.js
import SubTaskContainer from './sub-task/SubTaskContainer';

const routes = [
  {
    path: '/start',
    component: SubTaskContainer,
  },
  {
    path: '/',
    component: App,
    // ...
  },
];
JS

SubTask details

The SubTask component includes utility functions:

import {
  SUBTASK_SESSION_STORAGE, // session storage key
  getStoredSubTask, // get session storage value
  setStoredSubTask, // set session storage value
  resetStoredSubTask, // clear session storage value
}, SubTask from 'platform/forms/sub-task'; // SubTask component
JS

See the data section, Link To Data Section for more details.

Each page can include the following values:

{
  name: 'start', // required
  component: Component, // required
  validate: formData => true, // check before allowing "next" page
  back: null, // no back button
  next: 'page2', // continue to page2
}
JS
name

Required string name of page (usually stored in pageNames).

component

Required React component that includes form elements, links and information.

validate

Function with formData as a parameter.
Return a boolean value:

  • true for valid form data.

  • false for invalid form data. This prevents continuing, but does not prevent the back button from working.

If this function is omitted, the page is assumed to be valid and the Veteran can continue on with the sub-task flow

back & next buttons

Accepts:

  • null when no button is to be displayed

  • string page name to go to next

  • function uses formData parameter to determine destination page. Returns:

    • A string of the page name to go to

    • false to prevent next (not back) direction progress

    • null to not render the button

    • A url starting with / to target a destination page - uses router, so usually used to go to the introduction page, but may not work for pages outside the form

Considerations

Accessibility

Each sub-task page must include a unique H1 as this header will receive focus on page change.

The one-question per page pattern must be used within each sub-task page, and thus new content cannot dynamically appear based on the form value, e.g. content appearing below a radio button choice, with the exception of error messages or alerts.

The "Continue" button is a button, so when it behaves as a link when directing the Veteran to the form introduction page, it is not maintaining material honesty; but, we didn't want it to dynamically switch from a continue button into an action link as it would confuse the screen-reader user. And the design isn't appropriate.

Note: As of this writing (August 15, 2022), the va-radio component does not update as expected when a selection is made via a keyboard interaction (bug ticket #1096 addresses this problem).

Usage & Content

Please reference the sub-task pattern design spec for usage. This component only covers tasks before a flow. For tasks within a flow, use the array data (page) list loop pattern or "add item" link in array data pattern. We want to stay away from the in-page list loop pattern where it asks multiple questions on a single page, but what we want to follow is the new pattern of having a single question per page.

Analytics

Even with the deprecation of the wizard pattern, the analytics team directed us to continue using the same analytics monitor interactions for sub-tasks.

Data

While building this component, it was designed to make collection data as easy as possible. Therefore, all sub-task data is saved directly to the form.data.

Sub-task data is actually stored in 2 places:

  • Within the form.data

  • Within session storage

The reasoning for this is because of 2 scenarios:

  • If the Veteran is logged in, the sub-task saved to form.data will be available upon starting a new form.

  • If the Veteran is not logged in, the log in process will take the Veteran through multiple off-site pages and thus the React state is lost. Only the session storage value will remain.

Other considerations

  • Name any UI data with a view: prefix (e.g. view:isUploading) so that the value does not interfere with internal form data. The React JSON schema form library strips these values out before submission.

  • In the App component, get the sub-task data from the 2 sources (form.data > session storage) and if not set, re-route the Veteran to the /start page. See the previous "App & routing" section for more details and code examples.

Error messaging

The SubTask component will pass an error boolean value to the page if the validation function returns an error state and the page has been submitted (Continue button was activated).

If you want to show an error within the page upon blur, then treat the SubTask error value as a page submitted flag.

Removal

Removing a sub-task may be as simple as removing the redirect in the App.jsx, removing the /start path in the router, and deleting the sub-task folder.

Incorporating the sub-task page equivalent into the form flow may be necessary, depending on the circumstances.