Back to Cherry Studio

Preference Usage Guide

docs/references/data/preference-usage.md

2.0.06.7 KB
Original Source

Preference Usage Guide

This guide covers how to use the Preference system in React components and services.

React Hooks

usePreference (Single Preference)

typescript
import { usePreference } from '@data/hooks/usePreference'

// Basic usage - optimistic updates (default)
const [theme, setTheme] = usePreference('app.theme.mode')

// Update the value
await setTheme('dark')

// With pessimistic updates (wait for confirmation)
const [apiKey, setApiKey] = usePreference('api.key', { optimistic: false })

usePreferences (Multiple Preferences)

typescript
import { usePreferences } from '@data/hooks/usePreference'

// Read multiple preferences at once
const { theme, language, fontSize } = usePreferences([
  'app.theme.mode',
  'app.language',
  'chat.message.font_size'
])

Update Strategies

Optimistic Updates (Default)

UI updates immediately, then syncs to database. Automatic rollback on failure.

typescript
const [theme, setTheme] = usePreference('app.theme.mode')

const handleThemeChange = async (newTheme: string) => {
  try {
    await setTheme(newTheme) // UI updates immediately
  } catch (error) {
    // UI automatically rolls back
    console.error('Theme update failed:', error)
  }
}

Best for:

  • Frequent changes (theme, font size)
  • Non-critical settings
  • Better perceived performance

Pessimistic Updates

Waits for database confirmation before updating UI.

typescript
const [apiKey, setApiKey] = usePreference('api.key', { optimistic: false })

const handleApiKeyChange = async (newKey: string) => {
  try {
    await setApiKey(newKey) // Waits for DB confirmation
    toast.success('API key saved')
  } catch (error) {
    toast.error('Failed to save API key')
  }
}

Best for:

  • Security-sensitive settings (API keys, passwords)
  • Settings that affect external services
  • When confirmation feedback is important

PreferenceService Direct Usage

For non-React code or batch operations.

Renderer Process

typescript
import { preferenceService } from '@data/PreferenceService'

// Get single preference
const theme = await preferenceService.get('app.theme.mode')

// Get multiple preferences
const settings = await preferenceService.getMultiple([
  'app.theme.mode',
  'app.language'
])
// Returns: { 'app.theme.mode': 'dark', 'app.language': 'en' }

// Get with default value
const fontSize = await preferenceService.get('chat.message.font_size') ?? 14

Set Preferences

typescript
// Set single preference (optimistic by default)
await preferenceService.set('app.theme.mode', 'dark')

// Set with pessimistic update
await preferenceService.set('api.key', 'secret', { optimistic: false })

// Set multiple preferences at once
await preferenceService.setMultiple({
  'app.theme.mode': 'dark',
  'app.language': 'en',
  'chat.message.font_size': 16
})

Subscribe to Changes

typescript
// Subscribe to preference changes (useful in services)
const unsubscribe = preferenceService.subscribe('app.theme.mode', (newValue) => {
  logger.info('Theme changed to:', { theme: newValue })
})

// Cleanup when done
unsubscribe()

Main Process

In the main process, PreferenceService is lifecycle-managed. Access it via application.get():

typescript
import { application } from '@application'

const preferenceService = application.get('PreferenceService')

// Get/set preferences
const theme = preferenceService.get('app.theme.mode')
preferenceService.set('app.theme.mode', 'dark')

// Subscribe to changes
const unsubscribe = preferenceService.subscribeChange('app.theme.mode', (newValue) => {
  logger.info('Theme changed to:', { theme: newValue })
})

Note: Do NOT import PreferenceService directly in main process code. The renderer @data/PreferenceService is a separate singleton for the renderer process only.

Common Patterns

Settings Form

typescript
function SettingsForm() {
  const [theme, setTheme] = usePreference('app.theme.mode')
  const [language, setLanguage] = usePreference('app.language')
  const [fontSize, setFontSize] = usePreference('chat.message.font_size')

  return (
    <form>
      <select value={theme} onChange={e => setTheme(e.target.value)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
        <option value="system">System</option>
      </select>

      <select value={language} onChange={e => setLanguage(e.target.value)}>
        <option value="en">English</option>
        <option value="zh">中文</option>
      </select>

      <input
        type="number"
        value={fontSize}
        onChange={e => setFontSize(Number(e.target.value))}
        min={12}
        max={24}
      />
    </form>
  )
}

Feature Toggle

typescript
function ChatMessage({ message }) {
  const [showTimestamp] = usePreference('chat.display.show_timestamp')

  return (
    <div className="message">
      <p>{message.content}</p>
      {showTimestamp && <span className="timestamp">{message.createdAt}</span>}
    </div>
  )
}

Conditional Rendering Based on Settings

typescript
function App() {
  const [theme] = usePreference('app.theme.mode')
  const [sidebarPosition] = usePreference('app.sidebar.position')

  return (
    <div className={`app theme-${theme}`}>
      {sidebarPosition === 'left' && <Sidebar />}
      <MainContent />
      {sidebarPosition === 'right' && <Sidebar />}
    </div>
  )
}

Batch Settings Update

typescript
async function resetToDefaults() {
  await preferenceService.setMultiple({
    'app.theme.mode': 'system',
    'app.language': 'en',
    'chat.message.font_size': 14,
    'chat.display.show_timestamp': true
  })
}

Adding New Preference Keys

For a complete guide on adding new preferences, including naming conventions and design principles, see Preference Schema Guide.

Quick summary:

  1. Add custom types to preferenceTypes.ts (if needed)
  2. Add key to PreferenceSchemas interface in preferenceSchemas.ts
  3. Add default value to DefaultPreferences
  4. Use in code with full type safety

Best Practices

  1. Choose update strategy wisely: Optimistic for UX, pessimistic for critical settings
  2. Batch related updates: Use setMultiple when changing multiple related settings
  3. Provide sensible defaults: All preferences should have default values
  4. Keep values atomic: One preference = one logical setting
  5. Use consistent naming: Follow domain.feature.setting pattern

Preference vs Other Storage

ScenarioUse
User theme preferenceusePreference('app.theme.mode')
Window positionusePersistCache (can be lost without impact)
API keyusePreference with pessimistic updates
Search historyusePersistCache (nice to have)
Conversation historyDataApiService (business data)