feat(28-01): create useVuStore React hooks

- Add useAllVuLevels hook for subscribing to all VU levels
- Add useVuLevel hook for efficient single-mixer subscription
- Use useSyncExternalStore shim for React 16 compatibility
- Memoize getSnapshot callback to avoid unnecessary resubscription

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-03-03 20:20:26 +05:30
parent 5da85c1e79
commit 834472127d
1 changed files with 60 additions and 0 deletions

View File

@ -0,0 +1,60 @@
/**
* React hooks for subscribing to the external VU store
*
* Uses useSyncExternalStore shim for React 16 compatibility.
* Provides efficient subscription patterns for VU meter components.
*/
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { useCallback } from 'react';
import { vuStore } from '../stores/vuStore';
/**
* Subscribe to all VU levels
*
* WARNING: Use sparingly - causes re-render on ANY VU change.
* Prefer useVuLevel for single mixer subscription.
*
* @returns {Object} - Map of mixerId to { level, clipping, timestamp }
*
* @example
* function AllVuMeters() {
* const allLevels = useAllVuLevels();
* return Object.keys(allLevels).map(mixerId => (
* <VuMeter key={mixerId} mixerId={mixerId} level={allLevels[mixerId]} />
* ));
* }
*/
export function useAllVuLevels() {
return useSyncExternalStore(
vuStore.subscribe,
vuStore.getSnapshot
);
}
/**
* Subscribe to single mixer's VU level
*
* More efficient than useAllVuLevels - only re-renders when this specific
* mixer's level changes.
*
* @param {string} mixerId - Qualified mixer ID (e.g., "M123" or "P456")
* @returns {Object|null} - { level, clipping, timestamp } or null if mixer not found
*
* @example
* function VuMeter({ mixerId }) {
* const vuData = useVuLevel(mixerId);
* if (!vuData) return null;
* return <div>Level: {vuData.level}</div>;
* }
*/
export function useVuLevel(mixerId) {
// Create stable getSnapshot callback
// CRITICAL: Must be memoized with useCallback to avoid unnecessary resubscription
const getSnapshot = useCallback(() => vuStore.getLevelSnapshot(mixerId), [mixerId]);
return useSyncExternalStore(
vuStore.subscribe,
getSnapshot
);
}