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 dropdowns

createUseApplicationStoreState

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).

ValueDescription
isLoadingtrue until first value received from store
valueCurrent value (from store or local optimistic update)
setValueUpdates both local state and store (with optional debounce)

Debounce Guidelines

Choose debounce values based on input type for optimal user experience:

Input TypeDebounceReason
Text input250msWait for typing to pause
Textarea250msWait for typing to pause
Select/Dropdown0ms (default)Immediate feedback expected
Switch/Toggle0ms (default)Immediate feedback expected
Checkbox0ms (default)Immediate feedback expected
Radio0ms (default)Immediate feedback expected
Slider5msResponsive feel, reduced message traffic
Color picker5msResponsive 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

ParameterTypeDescription
storeSliceStoreSliceStore scope: store().instance, store().application, store().device, or store().shared(namespace)
keystringThe store key to sync with
initialValueTDefault value before store responds (optional)
debounceMsnumberDebounce 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 debounce