6. Build the weekly wellness reflection
What we are building
A short, app-owned FHIR Questionnaire with one self-report question for each domain. Ovok Native renders the Questionnaire, validates required answers and saves a FHIR QuestionnaireResponse for the signed-in Patient.
What you should already have
- The domain and provenance model from step 5.
- The current QuestionnaireResponseForm reference and FHIR Questionnaire and QuestionnaireResponse references.
- A project where the member can read the Questionnaire and create/read their QuestionnaireResponses.
The implementation
This is an example of an app-owned reflection, not a validated psychological, sleep, nutrition or clinical instrument. Keep it brief and neutral. Create and publish this resource once in the sandbox using an authorized project workflow; the mobile member flow should read the published resource, not create a new Questionnaire on every launch.
const reflectionOptions = [
"Much less than I wanted",
"A little less than I wanted",
"About as I expected",
"A little more than I expected",
"Much more than I expected",
];
export const weeklyWellnessQuestionnaire = {
resourceType: "Questionnaire" as const,
status: "active",
title: "Weekly wellbeing reflection",
subjectType: ["Patient"],
item: [
{
linkId: "sleep",
text: "How did your sleep feel this week?",
type: "choice" as const,
required: true,
answerOption: ["Very difficult", "Difficult", "Mixed", "Good", "Very good"].map(
(value) => ({ valueString: value }),
),
},
{
linkId: "movement",
text: "How did your movement compare with what you wanted for yourself?",
type: "choice" as const,
required: true,
answerOption: reflectionOptions.map((value) => ({ valueString: value })),
},
{
linkId: "nutrition",
text: "How did your eating habits feel this week?",
type: "choice" as const,
required: true,
answerOption: reflectionOptions.map((value) => ({ valueString: value })),
},
{
linkId: "stress",
text: "How would you describe your stress this week?",
type: "choice" as const,
required: true,
answerOption: ["Very high", "High", "Mixed", "Low", "Very low"].map(
(value) => ({ valueString: value }),
),
},
{
linkId: "recovery",
text: "How did your energy and sense of recovery feel this week?",
type: "choice" as const,
required: true,
answerOption: ["Very low", "Low", "Mixed", "Good", "Very good"].map(
(value) => ({ valueString: value }),
),
},
{
linkId: "week-note",
text: "What would you like to discuss with your coach?",
type: "string" as const,
},
],
};
Load the active Questionnaire from the project, then render it with the SDK-managed persistence path:
import { QuestionnaireForm } from "@ovok/native";
export function WeeklyReflectionForm({
questionnaire,
onCompleted,
}: {
questionnaire: Parameters<typeof QuestionnaireForm>[0]["questionnaire"];
onCompleted: (responseId: string) => void;
}) {
return (
<QuestionnaireForm
questionnaire={questionnaire}
onSuccess={(response) => {
if (response.id) onCompleted(response.id);
}}
onError={(error) => console.error("Reflection save failed", error)}
>
<QuestionnaireForm.Header />
<QuestionnaireForm.Content>
<QuestionnaireForm.Content.Item />
<QuestionnaireForm.Content.Error />
</QuestionnaireForm.Content>
<QuestionnaireForm.Navigation>
<QuestionnaireForm.Navigation.PreviousButton />
<QuestionnaireForm.Navigation.NextButton />
<QuestionnaireForm.Navigation.SubmitButton>
Save this reflection
</QuestionnaireForm.Navigation.SubmitButton>
</QuestionnaireForm.Navigation>
</QuestionnaireForm>
);
}
With onSuccess / onError and no app-owned onSubmit, the component creates a QuestionnaireResponse using the active Patient and the Questionnaire. A stable identifier prevents duplicate writes while a submission is pending; after a successful save, a later submission is a new response. Start a clean form for each weekly check-in rather than editing a completed response in place.
The example answer choices are ordinary app-owned reflection options, not a validated scale. The Questionnaire's item text is FHIR content; create a reviewed Questionnaire for each supported language rather than assuming CMS translation automatically rewrites FHIR resource content.
Important Ovok decisions
- Use stable
linkIdvalues so a question can be matched across responses and languages. - Keep the
Questionnaireresource as the question definition and eachQuestionnaireResponseas a distinct historical completion. - No score, diagnosis, psychological assessment or recommendation is calculated.
$populateand$extractare not required for this simple reflection. Use them only for a workflow that actually needs their documented behavior; see Ovok primitives.- App-owned labels and navigation are localized through CMS; Questionnaire content is managed as FHIR data.
Expected result
A member can complete the weekly reflection and the SDK saves a QuestionnaireResponse linked to that Patient and Questionnaire. Failed saves remain visible and can be retried.
Common errors and troubleshooting
| Symptom | Check |
|---|---|
| The form cannot render an item | Confirm the Questionnaire uses item types supported by the current QuestionnaireForm. |
| Submission says no profile is available | Mount the form after sign-in and successful Patient profile loading. |
| A retry creates duplicate responses | Keep the form instance mounted while the same submission is retried; mount a new instance only for a new check-in. |
| A language switch changes buttons but not question text | Publish a matching localized FHIR Questionnaire resource and load that version. |
| The form starts to look like a clinical assessment | Keep the questions neutral and clearly identify them as self-reflections. |