React Integration
Patterns for using the Vim Connect App SDK in React applications
The Vim Connect App SDK works with any JavaScript framework, but since most Vim apps are built with React, here are proven patterns for managing SDK state, subscriptions, and lifecycle.
SDK Provider Pattern
Wrap your app with a provider that handles initialization and makes the SDK available to all components:
// src/providers/vim-sdk-provider.tsx
'use client';
import { createContext, useContext, useEffect, useRef, useState } from 'react';
import { initVimSDK, type VimSDK } from '@vimconnect/app-sdk';
interface VimSDKContextValue {
sdk: VimSDK | null;
status: 'loading' | 'connected' | 'error';
error: string | null;
}
const VimSDKContext = createContext<VimSDKContextValue>({
sdk: null,
status: 'loading',
error: null,
});
export function useVimSDK() {
return useContext(VimSDKContext);
}
interface VimSDKProviderProps {
accessToken: string;
children: React.ReactNode;
}
export function VimSDKProvider({ accessToken, children }: VimSDKProviderProps) {
const [state, setState] = useState<VimSDKContextValue>({
sdk: null,
status: 'loading',
error: null,
});
const initRef = useRef(false);
useEffect(() => {
if (initRef.current) return;
initRef.current = true;
initVimSDK({ accessToken, debug: true })
.then((sdk) => {
sdk.hub.setActivationStatus('ENABLED');
setState({ sdk, status: 'connected', error: null });
})
.catch((err) => {
setState({ sdk: null, status: 'error', error: err.message });
});
}, [accessToken]);
return (
<VimSDKContext.Provider value={state}>
{children}
</VimSDKContext.Provider>
);
}Usage:
// src/app/app/page.tsx
function App({ accessToken }: { accessToken: string }) {
return (
<VimSDKProvider accessToken={accessToken}>
<PatientPanel />
</VimSDKProvider>
);
}Subscribing to Context Changes
The most common pattern is subscribing to context changes inside useEffect and syncing with React state:
// src/hooks/usePatientContext.ts
import { useEffect, useState } from 'react';
import { useVimSDK } from '@/providers/vim-sdk-provider';
export function usePatientContext() {
const { sdk } = useVimSDK();
const [patient, setPatient] = useState<any>(null);
useEffect(() => {
if (!sdk) return;
const unsubscribe = sdk.ehr.context.onChange(
'chart_open:patient',
(prev, curr) => {
setPatient(curr ?? null);
},
);
return unsubscribe;
}, [sdk]);
return patient;
}// src/components/patient-panel.tsx
function PatientPanel() {
const patient = usePatientContext();
if (!patient) return <div>No patient selected</div>;
const demo = patient.fields?.demographics;
return (
<div>
<h2>{demo?.firstName} {demo?.lastName}</h2>
<p>DOB: {demo?.dateOfBirth}</p>
</div>
);
}Subscribing to Workflow Events
import { useEffect, useState } from 'react';
import { useVimSDK } from '@/providers/vim-sdk-provider';
export function useWorkflowEvents() {
const { sdk } = useVimSDK();
const [events, setEvents] = useState<any[]>([]);
useEffect(() => {
if (!sdk) return;
const unsubscribe = sdk.ehr.workflow.on(
['chart_open', 'encounter_open'],
(event) => {
setEvents((prev) => [
{ type: event.type, timestamp: Date.now(), data: event },
...prev.slice(0, 49), // keep last 50
]);
},
);
return unsubscribe;
}, [sdk]);
return events;
}Performing Writeback
Writeback operations require checking capabilities and requesting permission. Here's a reusable pattern:
import { useCallback } from 'react';
import { useVimSDK } from '@/providers/vim-sdk-provider';
export function useEncounterWriteback() {
const { sdk } = useVimSDK();
const updateEncounter = useCallback(
async (
data: Record<string, any>,
fields: string[],
mode: 'override' | 'append' = 'override',
) => {
if (!sdk) throw new Error('SDK not initialized');
// Check capability
const cap = sdk.ehr.context.encounter.getCapability('update');
if (!cap.available) {
throw new Error(
cap.reason === 'not_in_context'
? 'No active encounter'
: 'Encounter updates not supported on this EHR',
);
}
// Request permission for disruptive operations
if (cap.disruptive && cap.permissionState === 'requestable') {
const result = await sdk.ehr.context.encounter.requestPermission('update', { fields });
if (result === 'denied') throw new Error('Permission denied by provider');
}
// Perform the update
return sdk.ehr.context.encounter.update(data, { mode });
},
[sdk],
);
return { updateEncounter };
}Usage in a component:
function AddDiagnosis({ icdCode, description }: { icdCode: string; description: string }) {
const { updateEncounter } = useEncounterWriteback();
const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
async function handleAdd() {
setStatus('saving');
try {
await updateEncounter(
{ assessment: { diagnoses: [{ code: icdCode, description }] } },
['assessment.diagnoses'],
'append',
);
setStatus('saved');
} catch (err: any) {
console.error('Writeback failed:', err.message);
setStatus('error');
}
}
return (
<button onClick={handleAdd} disabled={status === 'saving'}>
{status === 'saving' ? 'Adding...' : `Add ${icdCode}`}
</button>
);
}Hub Controls
import { useVimSDK } from '@/providers/vim-sdk-provider';
function NotificationButton() {
const { sdk } = useVimSDK();
function showAlert() {
sdk?.hub.pushNotification.show({
title: 'Action Required',
text: 'Review the care gap for this patient',
notificationId: `alert-${Date.now()}`,
type: 'warning',
timeoutInSec: 15,
actionButtons: {
rightButton: {
text: 'Review',
buttonStyle: 'PRIMARY',
openAppButton: true,
callback: () => console.log('Provider clicked Review'),
},
},
});
}
return <button onClick={showAlert}>Show Alert</button>;
}Tips
- Always clean up subscriptions. Return the
unsubscribefunction fromuseEffectto prevent memory leaks. - Use refs for initialization. Prevent double-initialization in React StrictMode with a
useRef(false)guard. - Keep the SDK instance stable. Initialize once in a provider, access everywhere via context. Don't call
initVimSDK()in multiple components. - Handle the loading state. The SDK takes a moment to connect — show a loading state until
status === 'connected'. - Wrap
useSearchParamsin Suspense. Next.js App Router requires aSuspenseboundary around components that useuseSearchParams().