Store Hooks
React hooks for reactive store state management with automatic subscriptions and cleanup
Store Hooks
React's hook system enables functional components to manage state, side effects, and lifecycle events without class components. The TelemetryOS SDK leverages this pattern by providing custom hooks that bridge React's reactive state model with the platform's persistent storage system. These hooks abstract away the complexity of subscribing to store changes, managing loading states, and cleaning up subscriptions when components unmount.
Import store hooks from @telemetryos/sdk/react.
Quick Start
// hooks/store.ts
import { createUseInstanceStoreState } from '@telemetryos/sdk/react'
// Create a typed hook for your store key
export const useTeamState = createUseInstanceStoreState<string>('team', '')// In any component
import { useTeamState } from '../hooks/store'
function MyComponent() {
const [isLoading, team, setTeam] = useTeamState()
return (
<input
disabled={isLoading}
value={team}
onChange={(e) => setTeam(e.target.value)}
/>
)
}Scope-Specific Factory Functions
These factory functions create reusable, typed hooks for specific store keys. Each function targets a specific storage scope and manages the store reference internally.
createUseInstanceStoreState
Creates hooks for instance-scoped storage. This is the most common choice for Settings ↔ Render communication.
import { createUseInstanceStoreState } from '@telemetryos/sdk/react'
// Create typed hooks for each store key
export const useTeamState = createUseInstanceStoreState<string>('team', '')
export const useLeagueState = createUseInstanceStoreState<string>('league', 'nfl')
export const useRefreshInterval = createUseInstanceStoreState<number>('refreshInterval', 30)// Use in components - add debounce for text inputs
const [isLoading, team, setTeam] = useTeamState(250) // 250ms debounce for text input
const [isLoading, league, setLeague] = useLeagueState() // 0ms default for dropdownscreateUseApplicationStoreState
Creates hooks for application-scoped storage. Data is shared across all instances of your application within an account.
import { createUseApplicationStoreState } from '@telemetryos/sdk/react'
// Shared across all instances
export const useGlobalConfig = createUseApplicationStoreState<Config>('globalConfig', defaultConfig)createUseDeviceStoreState
Creates hooks for device-scoped storage. Data persists on the specific device.
import { createUseDeviceStoreState } from '@telemetryos/sdk/react'
// Device-specific cache
export const useLocalCache = createUseDeviceStoreState<CacheData>('cache', {})Note: Device storage is only available in the Render mount point. Using it in Settings will throw an error.
createUseSharedStoreState
Creates hooks for shared storage with a namespace. Enables data sharing between different applications.
import { createUseSharedStoreState } from '@telemetryos/sdk/react'
// Inter-app communication
export const useSharedData = createUseSharedStoreState<SharedData>('data', {}, 'my-namespace')Return Value
All factory functions return a hook with this signature:
(debounceDelay?: number) => [isLoading: boolean, value: T, setValue: Dispatch<SetStateAction<T>>]
The default debounce delay is 0ms (immediate updates).
| Value | Description |
|---|---|
isLoading | true until first value received from store |
value | Current value (from store or local optimistic update) |
setValue | Updates both local state and store (with optional debounce) |
Debounce Guidelines
Choose debounce values based on input type for optimal user experience:
| Input Type | Debounce | Reason |
|---|---|---|
| Text input | 250ms | Wait for typing to pause |
| Textarea | 250ms | Wait for typing to pause |
| Select/Dropdown | 0ms (default) | Immediate feedback expected |
| Switch/Toggle | 0ms (default) | Immediate feedback expected |
| Checkbox | 0ms (default) | Immediate feedback expected |
| Radio | 0ms (default) | Immediate feedback expected |
| Slider | 5ms | Responsive feel, reduced message traffic |
| Color picker | 5ms | Responsive feel while dragging |
// Text input - debounce to wait for typing to pause
const [isLoading, team, setTeam] = useTeamState(250)
// Dropdown - immediate (default, no argument needed)
const [isLoading, league, setLeague] = useLeagueState()
// Slider - responsive (5ms)
const [isLoading, volume, setVolume] = useVolumeState(5)Benefits
- Type-safe: TypeScript knows the exact type of each store key
- Reusable: Same hook works in Settings and Render
- Automatic cleanup: No manual subscribe/unsubscribe needed
- Immediate sync: Changes sync to store automatically (no save button needed)
- Simple API: No need to pass store slice - just call the hook
Store Data Patterns
Recommended: Separate Store Entry Per Field
Create individual hooks for each configuration field:
// hooks/store.ts
export const useTeamState = createUseInstanceStoreState<string>('team', '')
export const useLeagueState = createUseInstanceStoreState<string>('league', 'nfl')
export const useShowScoresState = createUseInstanceStoreState<boolean>('showScores', true)This pattern is preferred because:
- Each field updates independently
- Simpler to reason about
- Better performance (only affected components re-render)
Alternative: Rich Data Object
For tightly related data (like slideshow items), use a single object:
// hooks/store.ts
interface SportsSlide {
team: string
league: string
displaySeconds: number
}
export const useSlidesState = createUseInstanceStoreState<SportsSlide[]>('slides', [])Use this pattern when:
- Data is inherently a collection (arrays, lists)
- Fields always change together
- You need atomic updates across multiple fields
Usage Examples
Settings View
import {
SettingsContainer,
SettingsField,
SettingsLabel,
SettingsInputFrame,
} from '@telemetryos/sdk/react'
import { useTeamState } from '../hooks/store'
export default function Settings() {
const [isLoading, team, setTeam] = useTeamState(250) // 250ms debounce for text input
return (
<SettingsContainer>
<SettingsField>
<SettingsLabel>Team Name</SettingsLabel>
<SettingsInputFrame>
<input
type="text"
placeholder="Enter team name..."
disabled={isLoading}
value={team}
onChange={(e) => setTeam(e.target.value)}
/>
</SettingsInputFrame>
</SettingsField>
</SettingsContainer>
)
}Render View
import { useTeamState, useLeagueState } from '../hooks/store'
export default function Render() {
// Use same hooks as Settings - automatically syncs when Settings changes
const [isLoadingTeam, team] = useTeamState()
const [isLoadingLeague, league] = useLeagueState()
if (isLoadingTeam || isLoadingLeague) return <div>Loading config...</div>
if (!team) return <div>Configure team in Settings</div>
return (
<div>
<h1>{team} - {league.toUpperCase()}</h1>
</div>
)
}Advanced
For cases requiring more control over the store slice, these lower-level APIs are available.
useStoreState
Direct hook that syncs React state with a store key. You pass the store slice explicitly.
import { useStoreState } from '@telemetryos/sdk/react'
import { store } from '@telemetryos/sdk'
function MyComponent() {
const [isLoading, value, setValue] = useStoreState<string>(
store().instance, // Store slice
'myKey', // Key name
'default value', // Initial state (optional)
300 // Debounce delay in ms (optional)
)
return (
<input
disabled={isLoading}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
)
}Parameters
| Parameter | Type | Description |
|---|---|---|
storeSlice | StoreSlice | Store scope: store().instance, store().application, store().device, or store().shared(namespace) |
key | string | The store key to sync with |
initialValue | T | Default value before store responds (optional) |
debounceMs | number | Debounce delay for setValue in milliseconds (optional) |
createUseStoreState
Factory function that creates hooks where you pass the store slice at call time.
import { createUseStoreState } from '@telemetryos/sdk/react'
import { store } from '@telemetryos/sdk'
// Create the hook
const useTeamState = createUseStoreState<string>('team', '')
// Use in component - pass store slice explicitly
const [isLoading, team, setTeam] = useTeamState(store().instance)
const [isLoading, team, setTeam] = useTeamState(store().instance, 500) // with debounceUpdated 19 days ago