(fix) O3-4681: Handle missing subforms gracefully to prevent null crash#703
(fix) O3-4681: Handle missing subforms gracefully to prevent null crash#703vivektarun wants to merge 2 commits into
Conversation
Problem:
When a form contained a subform page (isSubform: true) referencing
a form that does not exist on the backend, the form engine crashed
with: TypeError: Cannot read properties of null (reading 'markdown')
making the entire form unusable.
Changes:
- useFormJson.ts: Replace Promise.all with Promise.allSettled in
loadSubForms so a single missing subform does not crash the parent
form. Add null guard in updateFormJsonWithSubForms.
- form-engine.component.tsx: Add null guard for refinedFormJson
before rendering to prevent crash when form loading fails.
- encounter-form-processor.ts: Add null guard for page.sections
in prepareFormSchema and processFields to safely skip subform
pages that have no sections.
- page.renderer.component.tsx: Show an InlineNotification error
message when a subform page fails to load instead of crashing.
Added page.sections ?? [] guard to safely handle missing sections.
- common-utils.ts: Use Array.from(page.sections ?? []) to prevent
'undefined is not iterable' crash on subform pages.
- useFormJson.test.ts: Add test case verifying that the parent form
loads correctly and logs an error when a subform cannot be found.
Before: Form crashed with null reference error, white screen shown.
After: Parent form renders correctly. Missing subform page shows a
helpful error: 'Subform could not be loaded. Please verify
the form name is correct and that it is published.'
|
Thanks for working on this @vivektarun! I think beyond this PR, we should ensure that a subform doesn't create its own Encounter or Visit. |
|
Hi @samuelmale, thank you for the feedback! I understand the concern about subforms creating their own Encounter. Let me share my understanding of the problem and the proposed fix. Current wrong behaviour When a subform is loaded and the doctor submits the form, FormProcessorFactory is instantiated twice — once for the parent form and once for the subform. The subform instance has no reference to the parent's encounter, so it calls saveEncounter() independently and creates a brand new Encounter in the database. This results in two separate Encounter records for a single form submission — one holding the parent form observations and one holding the subform observations. When the doctor opens the form next week to edit, the form loads only the parent encounter and the subform fields appear empty because their observations are stored under a completely different encounter that the form has no way of finding. Proposed correct behaviour The subform should never create its own Encounter. Instead:
This way one single Encounter holds all observations from both the parent form and the subform, and editing works correctly next time. I have attached three diagrams below showing the wrong flow, the correct flow, and the database state comparison:
A few questions before I start coding this:
Happy to implement this if the approach looks correct. Thank you! |
ibacher
left a comment
There was a problem hiding this comment.
Thanks @vivektarun. I think most of my remarks are about comments here.
| // Guard: skip subform pages that have no sections | ||
| // (subform failed to load or is still a reference placeholder) |
There was a problem hiding this comment.
This comment is inaccurate as there's nothing in the code as written that makes this applicable to subforms.
| } | ||
|
|
||
| export default I18FormEngine; | ||
| export default I18FormEngine; No newline at end of file |
There was a problem hiding this comment.
| export default I18FormEngine; | |
| export default I18FormEngine; | |
| !isTrue(page.isHidden) && | ||
| page.subform?.form?.encounterType === formJson.encounterType | ||
| ) { | ||
| // Guard: skip if subform.form failed to load (is null/undefined) |
There was a problem hiding this comment.
I don't think this comment is particularly helpful
| * @param subForms - Array of loaded subform schemas | ||
| */ | ||
| function updateFormJsonWithSubForms(formJson: FormSchema, subForms: FormSchema[]): void { | ||
| // First populate successfully loaded subforms |
|
|
||
| /** | ||
| * Updates the parent form JSON with the loaded subforms. | ||
| * Includes a null guard to safely skip any undefined subform entries. |
There was a problem hiding this comment.
Please remove this second line.
| // Build a raw parent form that references a subform that does not exist on the backend. | ||
| // This simulates the exact scenario from O3-4681 where a subform name is wrong or | ||
| // the subform has not been published yet on the target server. |
There was a problem hiding this comment.
The first line of this comment is ok. The rest of it should be excised.
| // Show a clear error message when the subform could not be loaded. | ||
| // This helps admins/developers immediately understand what went wrong | ||
| // instead of seeing a blank page or a cryptic crash. |
There was a problem hiding this comment.
Does this comment explain something a programmer wouldn't already understand?
There was a problem hiding this comment.
Further note that although you may want to make this visible to admins or developers, like most error messages, this is probably mostly going to be seen by users who are neither. It's likely more helpful to say something like "Contact your system administrator."
There was a problem hiding this comment.
You are right on both points. The comment is self-evident from the code, so that I will remove it. I will also update the message to say something like 'Contact your system administrator' since end users will mostly see this.
| subtitle={t( | ||
| 'subformLoadErrorMessage', | ||
| 'The subform "{{subformName}}" could not be found on this server. Please verify the form name is correct and that it is published.', | ||
| { subformName: page.subform?.name }, |
There was a problem hiding this comment.
Is 'The subform "undefined" could not be found on this server.' a message we want to display?
There was a problem hiding this comment.
No, that is a bad user experience. I will add a guard so that if page.subform?.name is undefined it falls back to a generic message without showing the word undefined.
| // Guard: subform pages have no sections — return empty array | ||
| // so the rest of the component renders safely |
There was a problem hiding this comment.
This comment should be excised.
| // If this page is a subform page but the subform failed to load, | ||
| // show a helpful inline error instead of crashing. |
There was a problem hiding this comment.
I don't know that this comment is helpful.
- remove unhelpful inline comments - update formSessionIntent JSDoc - include subform name in error log - use type predicate instead of cast - fix undefined in error message - update error message for end users
|
Hi @ibacher, thank you for the detailed review! I have addressed all the comments. Here is a summary of the changes made:
All test cases are running
Please let me know if there is anything else to address. Thank you! |
|
@ibacher Would appreciate any feedback whenever you get time. Thank you! |




Summary
Fixes a crash where forms containing subform pages (
isSubform: true)referencing a form that does not exist on the backend threw:
TypeError: Cannot read properties of null (reading 'markdown')making the entire form unusable with a white screen error.
Related Issue
Fixes O3-4681
Root Cause
When
fetchOpenMRSFormreturned null for a missing subform:Promise.allinloadSubFormscrashed the entire form loadrefinedFormJsonbecame null but no null guard existed.sectionson subformpages which have no sections property
Changes
useFormJson.tsPromise.allSettled+ null guardsform-engine.component.tsxrefinedFormJsonencounter-form-processor.tspage.sectionspage.renderer.component.tsxInlineNotificationfor missing subformcommon-utils.tsArray.from(page.sections ?? [])useFormJson.test.tsBefore
Screen.Recording.2026-03-15.at.12.52.52.AM.1.mov
TypeError: Cannot read properties of null (reading 'markdown')After
Screen.Recording.2026-03-15.at.2.48.48.PM.mov
"Subform could not be loaded. Please verify the form name
is correct and that it is published."
Screenshots
Tests Passing
How to Test
{ "name": "Test Subform Parent", "version": "1", "published": false, "retired": false, "referencedForms": [], "allowUnspecifiedAll": true, "pages": [ { "label": "Lab Order", "isSubform": true, "subform": { "name": "HMIS LAB 001:General Laboratory Test" } }, { "label": "Main Section", "sections": [ { "label": "Test Section", "isExpanded": true, "questions": [ { "label": "Notes", "type": "obs", "id": "notes", "questionOptions": { "rendering": "textarea", "concept": "160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" } } ] } ] } ] }Checklist