@awell-health/navi-js-react
Version:
React components and hooks for integrating Navi care flows
454 lines (358 loc) • 10.3 kB
Markdown
npm # -health/navi-react
> React components and hooks for integrating Navi care flows
[](https://www.npmjs.com/package/@awell-health/navi-react)
[](https://bundlephobia.com/package/@awell-health/navi-react)
## What is this package?
This is the **React SDK** for Navi care flows. Instead of using the iframe-based loader script, this package provides native React components and hooks that integrate seamlessly with your React application.
**Key features:**
- ⚛️ **Native React integration** - Components, hooks, and TypeScript support
- 🔧 **Declarative API** - Use JSX components instead of imperative JavaScript
- 🎣 **React Hooks** - `useFlowEmbed` for programmatic control
- 📱 **SSR Compatible** - Works with Next.js, Remix, and other React frameworks
- 🔒 **Type Safe** - Full TypeScript definitions included
- 🎯 **15KB bundle size** - Optimized for performance
## When to use this package?
Choose `-health/navi-react` when:
- ✅ You're building a React/Next.js application
- ✅ You want native React components instead of iframes
- ✅ You need tight integration with React state/lifecycle
- ✅ You want TypeScript support out of the box
Use [`-health/navi`](../navi-loader) (the loader script) when:
- ❌ You're not using React
- ❌ You want the simplest possible integration
- ❌ You need to embed flows in existing non-React pages
## Installation
```bash
npm install -health/navi-react
# or
yarn add -health/navi-react
# or
pnpm add -health/navi-react
```
## Quick Start
### 1. Wrap your app with NaviProvider
```jsx
import { NaviProvider } from "@awell-health/navi-react";
function App() {
return (
<NaviProvider publishableKey="pk_test_your_key_here">
<YourApp />
</NaviProvider>
);
}
```
### 2. Use the FlowEmbed component
```jsx
import { FlowEmbed } from "@awell-health/navi-react";
function OnboardingPage() {
return (
<div>
<h1>Complete Your Health Assessment</h1>
<FlowEmbed
flowId="health_assessment_123"
onFlowCompleted={(data) => {
console.log("Assessment completed!", data);
// Redirect or update UI
}}
/>
</div>
);
}
```
## Complete Example
```jsx
import React, { useState } from "react";
import {
NaviProvider,
FlowEmbed,
useFlowEmbed,
} from "@awell-health/navi-react";
// Component using FlowEmbed
function HealthScreening() {
const [isCompleted, setIsCompleted] = useState(false);
const handleActivityCompleted = (data) => {
console.log("Activity completed:", data.activityId);
};
const handleFlowCompleted = (data) => {
console.log("Screening completed!");
setIsCompleted(true);
};
const handleError = (error) => {
console.error("Screening error:", error);
alert("Something went wrong. Please try again.");
};
if (isCompleted) {
return <div>✅ Health screening completed!</div>;
}
return (
<FlowEmbed
flowId="health_screening_456"
className="my-flow-styles"
options={{
context: {
patientId: "patient_123",
source: "react-app",
},
}}
onActivityCompleted={handleActivityCompleted}
onFlowCompleted={handleFlowCompleted}
onError={handleError}
/>
);
}
// Component using useFlowEmbed hook
function DynamicFlow() {
const [flowId, setFlowId] = useState("");
const { embed, destroy, isEmbedded, error } = useFlowEmbed();
const handleEmbed = () => {
embed(flowId, "#dynamic-container", {
context: { timestamp: Date.now() },
});
};
return (
<div>
<input
value={flowId}
onChange={(e) => setFlowId(e.target.value)}
placeholder="Enter flow ID"
/>
<button onClick={handleEmbed} disabled={!flowId}>
Embed Flow
</button>
<button onClick={destroy} disabled={!isEmbedded}>
Remove Flow
</button>
{error && <div>Error: {error.message}</div>}
<div id="dynamic-container" style={{ minHeight: 300 }} />
</div>
);
}
// Main App
function App() {
return (
<NaviProvider
publishableKey="pk_test_your_key_here"
debug={true} // Enable debug mode in development
>
<div className="app">
<h1>My Healthcare App</h1>
<HealthScreening />
<DynamicFlow />
</div>
</NaviProvider>
);
}
export default App;
```
## API Reference
### `<NaviProvider>`
Provides Navi context to all child components. Must wrap any components using Navi.
```jsx
<NaviProvider
publishableKey="pk_test_your_key"
apiUrl="https://api.navi.awell.com" // optional
debug={false} // optional
>
<App />
</NaviProvider>
```
**Props:**
- `publishableKey` (string, required) - Your Navi publishable key
- `apiUrl` (string, optional) - Custom API URL (for testing)
- `debug` (boolean, optional) - Enable debug logging
- `children` (ReactNode, required) - Your app components
### `<FlowEmbed>`
Renders a care flow as a React component.
```jsx
<FlowEmbed
flowId="flow_123"
className="my-styles"
options={{ context: { userId: "123" } }}
onActivityLoaded={(data) => {}}
onActivityCompleted={(data) => {}}
onFlowCompleted={(data) => {}}
onError={(error) => {}}
/>
```
**Props:**
- `flowId` (string, required) - The care flow ID to render
- `className` (string, optional) - CSS class for styling
- `options` (object, optional) - Flow configuration
- `context` (object) - Additional context data
- `onActivityLoaded` (function, optional) - Called when activity loads
- `onActivityCompleted` (function, optional) - Called when activity completes
- `onFlowCompleted` (function, optional) - Called when entire flow completes
- `onError` (function, optional) - Called when errors occur
### `useNavi()`
Hook to access Navi context and loading state.
```jsx
const { config, isLoaded, error } = useNavi();
```
**Returns:**
- `config` - The Navi configuration (publishableKey, apiUrl, debug)
- `isLoaded` - Boolean indicating if Navi script is loaded
- `error` - Any loading errors
### `useFlowEmbed()`
Hook for programmatic flow embedding and management.
```jsx
const { embed, destroy, isEmbedded, error } = useFlowEmbed();
```
**Returns:**
- `embed(flowId, container, options)` - Function to embed a flow
- `destroy()` - Function to remove the embedded flow
- `isEmbedded` - Boolean indicating if a flow is currently embedded
- `error` - Any embedding errors
## Integration Patterns
### Next.js App Router
```jsx
// app/layout.tsx
import { NaviProvider } from '-health/navi-react';
export default function RootLayout({ children }) {
return (
<html>
<body>
<NaviProvider publishableKey={process.env.NEXT_PUBLIC_NAVI_KEY}>
{children}
</NaviProvider>
</body>
</html>
);
}
// app/onboarding/page.tsx
import { FlowEmbed } from '-health/navi-react';
export default function OnboardingPage() {
return (
<div>
<h1>Welcome!</h1>
<FlowEmbed flowId="onboarding_flow" />
</div>
);
}
```
### Conditional Rendering
```jsx
function PatientDashboard({ patient }) {
const showHealthCheck = patient.needsHealthCheck;
return (
<div>
<h1>Dashboard</h1>
{showHealthCheck && (
<FlowEmbed
flowId="daily_health_check"
options={{ context: { patientId: patient.id } }}
onFlowCompleted={() => {
// Refresh patient data
mutate(`/api/patients/${patient.id}`);
}}
/>
)}
{/* Rest of dashboard */}
</div>
);
}
```
### Error Handling
```jsx
function RobustFlow() {
const [hasError, setHasError] = useState(false);
if (hasError) {
return (
<div className="error-state">
<p>Unable to load care flow.</p>
<button onClick={() => setHasError(false)}>Try Again</button>
</div>
);
}
return (
<FlowEmbed
flowId="sensitive_flow"
onError={(error) => {
console.error("Flow error:", error);
setHasError(true);
}}
/>
);
}
```
### TypeScript Usage
```tsx
import {
NaviProvider,
FlowEmbed,
FlowEmbedProps,
useNavi,
} from "@awell-health/navi-react";
interface CustomFlowProps {
patientId: string;
flowType: "intake" | "followup" | "discharge";
}
const CustomFlow: React.FC<CustomFlowProps> = ({ patientId, flowType }) => {
const { isLoaded } = useNavi();
const handleCompleted = (data: { flowId: string; completedAt: string }) => {
console.log("Flow completed:", data);
};
if (!isLoaded) {
return <div>Loading Navi...</div>;
}
return (
<FlowEmbed
flowId={`${flowType}_flow`}
options={{
context: { patientId },
}}
onFlowCompleted={handleCompleted}
/>
);
};
```
## Styling
The components can be styled with CSS:
```css
/* Style the flow container */
.navi-flow-container {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
}
/* Loading state */
.navi-loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
color: #666;
}
/* Error state */
.navi-error {
background: #fee;
border: 1px solid #fcc;
padding: 15px;
border-radius: 4px;
color: #c00;
}
```
## Performance
- **Bundle size**: ~15KB gzipped
- **Code splitting**: Import only what you need
- **SSR compatible**: Works with server-side rendering
- **Lazy loading**: Flow content loads on demand
## Browser Support
- React 16.8+ (hooks required)
- Modern browsers (same as Navi loader)
## Migration from Loader Script
If you're migrating from `-health/navi`:
```jsx
// Before (with loader script)
const navi = Navi("pk_test_key");
navi.renderFlow("flow_123", "#container");
// After (with React)
<NaviProvider publishableKey="pk_test_key">
<FlowEmbed flowId="flow_123" />
</NaviProvider>;
```
## Support
- 📖 [Documentation](https://docs.navi.awell.com/react)
- 💬 [Community Support](https://github.com/awell-health/navi/discussions)
- 🐛 [Report Issues](https://github.com/awell-health/navi/issues)
## License
MIT