VA Forms Library - How to bypass Schema Form
What does "bypassing the SchemaForm" mean?
The schema-based forms library in vets-website
renders a component called SchemaForm
. This component uses react-jsonschema-form
to render form fields using the schema
and uiSchema
for a page in the formConfig
.
Bypassing the SchemaForm
component means you can render a form page using plain React components. Minimal schema
, and uiSchema
should include any required fields and validations.
You may bypass SchemaForm
for one page at a time. This means you can use schema
and uiSchema
when it's helpful, and ignore it when it isn't.
How to bypass SchemaForm
In your form's formConfig
for a given page, you may supply CustomPage
and CustomPageReview
properties, which contain React components.
Example
// form/config.js
import CustomPage from './CustomPage'; // React component
import CustomPageReview from './CustomPageReview'; // React component
export const formConfig = {
// Some config here...
chapters: {
chapterOneName: {
// Chapter config here...
pages: {
mySchemalessPage: {
path: 'my-schemaless-page',
title: 'Bypassing the SchemaForm',
CustomPage,
CustomPageReview,
schema: { // This does still need to be here or it'll throw an error
type: 'object',
properties: {}, // The properties can be empty
},
uiSchema: {},
}
}
}
}
}
There are a few important things going on here:
We pass
CustomPage
to the page's config.We pass
CustomPageReview
to the page's config.We still supply a
schema
anduiSchema
, but leaveuiSchema
(essentially) empty; if we leave them out entirely, the library will throw an error.
Defining Schema for a CustomPage
In this example we’re defining a schema to be used in a CustomPage form. Because there is no CustomPageReview
, the schema for the page must be defined. This gets passed through to the props.data
object in the CustomPage component.
...
import MyCustomPage from '../components/MyCustomPage';
const formConfig = {
...
pages: {
bypassSchemaPage1: {
path: 'bypass-schema-page-1',
title: 'Add Emergency Contact Information',
CustomPage: MyCustomPage,
uiSchema: {}, // UI schema is completely ignored
schema: {
type: 'object',
properties: {
phoneNumber: {
type: 'string',
},
},
},
},
},
...
}
export default formConfig;
CustomPage props
name
: StringThe name of the page in the
formConfig
.In the example above, it's
bypassSchemaPage1
.
title
: StringThe title of the page in the
formConfig
.In the example above, it's
Add Emergency Contact Information
.
data
: ObjectThe entire form data.
Important: It contains the form data for the entire form, not just for the page.
pagePerItemIndex
: Number | undefinedIf
showPagePerItem
is true for the page, this contains the array index.
onReviewPage
: BooleanTrue if the page is being rendered in edit mode on the review page.
This is useful for rendering either the form navigation buttons or the update button on the review page.
In the Common tasks section below, onReviewPage is used to display custom information for both CustomPage and ReviewPage that share the same view logic:
onReviewPage ? updatePage() : goToPath(returnPath);
trackingPrefix
: StringThe tracking prefix specified in for the form in
formConfig
.
uploadFile
: FunctionThe function to call to upload a file.
Not supplied when
CustomPage
is rendered on the review page.
goBack
: () => voidCustomPage
only.The function to call to move back a page in the form.
Not supplied when
CustomPage
is rendered on the review page.In the Common tasks section below,
goForward
is supplied to theFormNavButtons
to render the “Go Back” button.
goForward
: () => voidCustomPage
only.The function to call to move forward a page in the form.
Caveat: Rather than assigning this to a button's
onClick
event handler, make that button a submit button and use<form onSubmit={goForward}>...</form>
so the user only navigates to the next page if there are no validation errors.
goToPath
: FunctionCustomPage
only.Function to call if you need to go to a specific path within the form.
When calling this function with a valid active path, it will return you to that page. For example,
goToPath('/second-page').
The path can also include search parameters which would allow navigating between CustomPages, and pass a reference; example:
goToPath('/view?id=1234')
An invalid path will send you back one page, so be careful.
updatePage
: () => voidEdit mode on on review page (
CustomPage
) only.Supplied only when rendering
CustomPage
on the review page.The function to call when the user is finished editing the data.
In the Common tasks section below,
updatePage
is used to update the review page:onReviewPage ? updatePage() : ...
editPage
: () => voidCustomPageReview
only.The function to call to initiate editing the data on the review page.
contentBeforeButtons
: ElementRenders the "Finish this application later" link as well as the save-in-progress menu link (when enabled)
contentAfterButtons
: ElementRenders the
SaveStatus
component which shows the time and date of the last time the application was saved
setFormData
: FunctionCalls the
setData
actionThis function also auto-saves the form data change, which in turn updates the
SaveStatus
date and time
schema
: ObjectSchema for the custom page
Usually an object type with an empty property, unless required fields are needed to check validation on the review & submit page
uiSchema
: ObjectUI schema for custom page
Usually empty.
Add any ui settings (e.g.
ui:option
,ui:required
,ui:validation
, etc) entries needed to check validation or settings to change review page rendering
The Review Page
There are three options for showing your page content on the Review Page:
Use the
CustomPageReview
.Automatically render the page content using
schema
anduiSchema
. Important: You may still useCustomPage
with this option; see the Using scheme and uiSchema section below.Hide the page entirely.
Using CustomPageReview
To bypass the automatic data rendering, you may supply a React component to CustomPageReview
in your page's config. See CustomPage propsfor the props that will be passed to this component.
Note: You may use CustomPageReview
with or without CustomPage
. If your page has a CustomPage
component and CustomPageReview
, the schema
and uiSchema
will be ignored altogether, so you don't need to spend time writing them.
Using schema
You may bypass the SchemaForm
on the form page but still retain the automatic review page data rendering by doing the following in your page's config:
Add the
schema
as normal.Set
CustomPageReview
tonull
.
An example of this type of form config can be found in the above section Custom Page Schema and the below example outlining Common tasks.
Note: If there is a CustomPage
, but CustomPageReview
is undefined
and schema.properties
is empty, the Forms Library will throw an error. This is to make sure you're deliberately choosing to supply a custom page using automatic data rendering or hiding the page.
Hiding the page
If you want the page to appear in the normal form flow but not on the review page, do the following in your page's config:
Supply a
CustomPage
.Set
CustomPageReview
tonull
.Set
schema.properties
to{}
.
Common tasks
There are a few common tasks you'll need to perform when bypassing the SchemaForm
.
Adding values to the data object
When using a CustomPage
, you’ll need to call setData
to update form data, along with adding any validation that’s required to reach the next page.
Example
The following example is a simplified version of this CustomForm.
// CustomPage.jsx
import React from 'react';
import { setData } from 'platform/forms-system/src/js/actions';
import { TextField } from '@department-of-veterans-affairs/formulate';
import FormNavButtons from '~/platform/forms-system/src/js/components/FormNavButtons';
const CustomPage = ({
title,
data,
onReviewPage,
goBack,
goToPath,
updatePage,
setFormData, // setData
}) => {
// Set state for this form
const [phoneObj, setphoneObj] = useState({
value: data.phoneNumber || '',
dirty: false,
}
);
...
const updateFormData = event => {
... // Do some kind of validation before setting form data
// use setData to set form schema data
setFormData({ ...data, phoneNumber: phoneObj.value});
// Redirect to page using goToPath, or if on review page,
// return to review mode instead of edit mode.
onReviewPage ? updatePage() : goToPath(returnPath);
}
const onPhoneChange = updatedPhoneValue => {
setPhoneObject(updatedPhoneValue);
}
...
const navButtons = <FormNavButtons goBack={goBack} submitToContinue/>;
const updateButton = <button type="submit">Review update button</button>;
return (
<form onSubmit={updateFormData}>
<fieldset>
<legend
id="emergency-contact-date-description"
className="vads-u-font-family--serif"
name="addIssue"
>
{ title }
</legend>
<TextInput
id="add-ec"
name="add-ec"
type="text"
label={myCustomEmergencyContactPhoneLabel}
required
field={phoneObj}
onValueChange={onPhoneChange}
errorMessage={
(submitted || phoneObj.dirty) // do some kind of validation here
}
/>
{onReviewPage ? updateButton : navButtons}
</fieldset>
</form>
)
};
// map setData to component props
const mapDispatchToProps = {
setFormData: setData,
};
export default connect(
null,
mapDispatchToProps,
)(CustomPage);
There are a few important parts to note here:
We call a custom submit function which calls the forms-system
setData
which sets the data object for this part of the form schema.The navigation buttons are imported from here.
goForward
is supplied to theonSubmit
event handler prop andsubmitToContinue
is passed toFormNavButtons
.This is so the
Continue
button istype="submit"
and navigation only occurs if there are no validation errors.If
const navButtons = <FormNavButtons goBack={goBack} goForward={goForward} />
, then the user would continue to the next page even if there were validation errors.
updateButton
istype="submit"
for the same reasons outlined above.{onReviewPage ? updateButton : navButtons }
renders the context-appropriate buttons depending on where theCustomPage
is rendered.
Render the edit button on CustomPageReview
const AddEmergencyContactReview = ({ data, editPage }) => (
<>
<h1>
Hello, {data.phoneNumber} is your emergency contact info phone number.
</h1>
<button onClick={editPage}>
Edit
</button>
</>
);
export default AddEmergencyContactReview;
There's less going on here. Just make sure your CustomPageReview
has an edit button that calls editPage
if the user needs to be able to edit the data on that page.
Help and feedback
Get help from the Platform Support Team in Slack.
Submit a feature idea to the Platform.