Introduction
This documentation covers files located at: Questionnaire Renderer
General Information
The Questionnaire Renderer is designed to render FHIR Questionnaires as well as display and edit existing QuestionnaireResponse elements.
Since we built the renderer from scratch without relying on external libraries, we theoretically have the flexibility to render custom elements, integrate the UI according to our specific design, and manage the localization logic ourselves.
However, because of this approach, it is crucial to understand the inherent difficulties and complexity dictated by the FHIR Questionnaire and QuestionnaireResponse data structures.
Grundsätzlicher Aufbaue der beiden Datenkonstrukte
The Questionnaire serves as the configuration for the entire form. To display the form, all desired elements are configured as "Questionnaire Items."
FHIR Questionnaire Item Examples
In a FHIR Questionnaire, the item element is recursive. This allows for simple flat lists or complex hierarchical structures. Below are three common patterns used in clinical data capture.
1. Simple String Item
The most basic pattern. Use type: "string" for short, single-line text inputs.
{
"linkId": "patient-nickname",
"text": "Preferred Name / Nickname",
"type": "string",
"required": false
}
2. Group with Multiple Strings
Groups (type: "group") allow you to organize related questions under a single header. This is essential for UI rendering (e.g., sections or cards).
{
"linkId": "address-section",
"text": "Mailing Address",
"type": "group",
"item": [
{
"linkId": "street-address",
"text": "Street and House Number",
"type": "string"
},
{
"linkId": "city",
"text": "City",
"type": "string"
},
{
"linkId": "zip-code",
"text": "Zip Code",
"type": "string"
}
]
}
3. Nested String (Question-under-Question)
FHIR allows you to nest items inside other items that are not groups. This is often used for follow-up details. In many renderers, the child item only becomes relevant if the parent item has a value.
{
"linkId": "current-medication",
"text": "Are you currently taking any medication? If so, which one?",
"type": "string",
"item": [
{
"linkId": "dosage-instruction",
"text": "What is the prescribed dosage for this medication?",
"type": "string"
}
]
}
Das sieht auf den ersten Blick nach einer relativ einfach abzubildenden Struktur aus, die daraus entstehnde Response jedoch ist komplexer aufgebaut als man denkt.
When an item is nested inside another (like a follow-up question), the QuestionnaireResponse mirrors this hierarchy. The nested question appears inside the item property of the parent's answer.
This example shows a full QuestionnaireResponse with nested questions for Example 3.
{
"resourceType": "QuestionnaireResponse",
"status": "completed",
"item": [
{
"linkId": "current-medication",
"text": "Are you currently taking any medication? If so, which one?",
"answer": [
{
"valueString": "Lisinopril",
"item": [
{
"linkId": "dosage-instruction",
"text": "What is the prescribed dosage for this medication?",
"answer": [
{
"valueString": "10mg once daily"
}
]
}
]
}
]
}
]
}
Notice that the nested linkId: "dosage-instruction" is placed inside the answer object of the parent. This reinforces that the dosage is directly related to the specific medication "Lisinopril".
Repetition of Items
The complexity of rendering a FHIR-based form primarily arises from the ability to repeat items. This feature, controlled by a simple repeats: true boolean flag, allows users to fill out a field or an entire group multiple times.
The Structural Complexity
The challenge begins with the fact that repeated instances only exist within the QuestionnaireResponse. Since child elements are nested directly within their parent's answers, they are duplicated in the response as well. This leads to several critical structural differences:
- Non-Unique linkIds: While
linkIdis unique within the Questionnaire (the schema), it is no longer unique within the Response (the instance) if items repeat. - Sparse Trees: A
QuestionnaireResponsedoes not contain the full tree structure of the Questionnaire; it only includes items that have been populated with data. - Shifted Nesting: The Response uses a different nesting logic than the Questionnaire to maintain the association between specific answers and their nested sub-elements.
Key Considerations for Implementation
When documenting your rendering logic, keep the following points in mind:
- Questionnaire Structure: Every
linkIdis unique, and each item definition exists only once. - Response Density: The Response is often incomplete, as it omits unanswered items.
- Cardinality: The Response can contain multiple items with the same
linkId, breaking the uniqueness found in the schema. - Hierarchical Alignment: The Response structure changes to accommodate repetitions and ensure that nested elements are correctly mapped to their specific parent answers.
Conclusion for Frontend Rendering
Effectively rendering a form requires a hybrid approach. The frontend must combine the Questionnaire configuration (the "what") with the QuestionnaireResponse (the "how many"). Even if a response does not exist yet, the form must be capable of dynamically altering its rendered structure as the user interacts with repeating elements.
The linkId of the items serves as the unique identifier. It is mandatory to set this value and ensure it is unique within the entire Questionnaire configuration.
Duplicate IDs will result in incorrect form rendering and data corruption.
Questionnaire and QuestionnaireResponse Relationship
The following high-level diagram illustrates the relationships and dependencies described above:
Our Questionnaire Renderer Architecture
The different layers of our renderer are illustrated in this diagram. Each element is described in technical detail on its respective page.