` wrapper needed
* Your styles are scoped to the Shadow DOM automatically — they won't leak out or be affected by the host page
* Use `var(--color-action-primary-default, #9254D9)` to adopt the community's branding via design tokens
### 5. Bundle for Production
```bash
npm run build
```
This produces `dist/widget.js` — an ES module ready to be published. Include it in your widget repository alongside the HTML template and [Widget Definition Reference](widget-schema) entry.
## Working with Props
When props change at runtime (e.g. an editor updates configuration in the **No-Code Builder**), the `sdk` object emits a `propsChanged` event. Use `useState` and `useEffect` to keep your component in sync:
```typescript
import { useState, useEffect } from 'react'
import type { WidgetSDK } from './types/widget-sdk'
function MyWidget({ sdk }: { sdk: WidgetSDK }) {
const [props, setProps] = useState(sdk.getProps<{ title: string }>())
useEffect(() => {
const unsubscribe = sdk.on('propsChanged', (newProps) => {
setProps(newProps as { title: string })
})
return unsubscribe
}, [sdk])
return
{props.title}
}
```
The `on()` method returns an unsubscribe function, which aligns perfectly with `useEffect` cleanup.
## Advanced Patterns
### Custom Hook: `useWidgetProps`
Extract the props subscription into a reusable hook so every component gets reactive props without duplicating the `on('propsChanged')` boilerplate:
**`src/hooks/useWidgetProps.ts`**
```typescript
import { useState, useEffect } from 'react'
import type { WidgetSDK } from '../types/widget-sdk'
export function useWidgetProps
(sdk: WidgetSDK): T {
const [props, setProps] = useState(sdk.getProps())
useEffect(() => {
const unsubscribe = sdk.on('propsChanged', (newProps) => {
setProps(newProps as T)
})
return unsubscribe
}, [sdk])
return props
}
```
**Usage in any component:**
```typescript
function MyWidget({ sdk }: { sdk: WidgetSDK }) {
const props = useWidgetProps<{ title: string }>(sdk)
return {props.title}
}
```
### Custom Hook: `useWidgetSDK`
Create a React context to make the `sdk` object available throughout your component tree without prop drilling:
**`src/hooks/useWidgetSDK.tsx`**
```typescript
import { createContext, useContext, type ReactNode } from 'react'
import type { WidgetSDK } from '../types/widget-sdk'
const WidgetSDKContext = createContext(null)
export function WidgetSDKProvider({ sdk, children }: { sdk: WidgetSDK; children: ReactNode }) {
return (
{children}
)
}
export function useWidgetSDK(): WidgetSDK {
const sdk = useContext(WidgetSDKContext)
if (!sdk) {
throw new Error('useWidgetSDK must be used within a WidgetSDKProvider')
}
return sdk
}
```
**Usage in `init`:**
```typescript
export async function init(sdk: WidgetSDK) {
await sdk.whenReady()
const root = createRoot(sdk.getContainer())
root.render(
)
sdk.on('destroy', () => root.unmount())
}
```
**Usage in any child component:**
```typescript
function UserGreeting() {
const sdk = useWidgetSDK()
const props = sdk.getProps<{ username: string }>()
return Hello, {props.username}!
}
```
### Error Boundaries
Wrap your widget in an error boundary to prevent crashes from taking down the host page:
**`src/components/WidgetErrorBoundary.tsx`**
```typescript
import { Component, type ErrorInfo, type ReactNode } from 'react'
interface ErrorBoundaryProps {
fallback?: ReactNode
children: ReactNode
}
interface ErrorBoundaryState {
hasError: boolean
}
export class WidgetErrorBoundary extends Component {
state: ErrorBoundaryState = { hasError: false }
static getDerivedStateFromError(): ErrorBoundaryState {
return { hasError: true }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('[Widget Error]', error, errorInfo)
}
render() {
if (this.state.hasError) {
return this.props.fallback || Something went wrong.
}
return this.props.children
}
}
```
**Usage in `init`:**
```typescript
export async function init(sdk: WidgetSDK) {
await sdk.whenReady()
const root = createRoot(sdk.getContainer())
root.render(
)
sdk.on('destroy', () => root.unmount())
}
```
## React Tips
For general widget best practices (Shadow DOM, styling, cleanup, bundle size), see [Widget Runtime](core-concepts#best-practices). The tips below are React-specific.
* **`await sdk.whenReady()` before mounting**. This ensures the `sdk` object is fully initialized. After that, call `createRoot` and `render` — let React handle async rendering from there.
* **Use `React.lazy` and `Suspense`** for code-splitting within large widgets.
* **Memoize expensive computations** with `useMemo` and `useCallback`.
* **Always call `root.unmount()`** in the `destroy` handler. Failing to do so leaks React's internal state.
* **Ensure React is bundled** in your widget output — the platform does not provide it.
For common widget issues (mount point, styles, props, module errors), see [Common Issues](common-issues#widget-development-issues).
## Template Repository
For a complete working React widget (project structure, Vite config, build output, and import map setup), see the [React widget in the template repository](https://github.com/gainsight-hub/widgets-repository-template/tree/main/widgets/react_widget). Fork it to get started quickly.
## Next Steps
* [Widget Runtime](core-concepts) — How widgets are loaded, the `sdk` object's API, and the widget lifecycle
* [Widget Definition Reference](widget-schema) — Define your widget in `extensions_registry.json`
* [Configurable Widgets](configurable-widgets) — Let editors customize your widget via a form in the **No-Code Builder**
* [Repository Layout](project-setup) — How to organize widget files in your repository