BloxwapSFX
Guides

React

Optional hooks for binding sounds, stable play handlers, and persisted preferences.

The optional @bloxwap/sfx/react entry supports React 18 and 19. React is an optional peer; importing the core package keeps React out of its bundle.

Bind a subtree

'use client';
import { useRef } from 'react';
import { useBindSounds } from '@bloxwap/sfx/react';

export function Toolbar() {
  const ref = useRef<HTMLDivElement>(null);
  useBindSounds(ref, { keyboard: true, hoverInterval: 150 });
  return <div ref={ref}><button data-sound-press data-sound-release>Save</button></div>;
}

The hook binds after mount, rebinds when options change, and cleans up on unmount. This also works with React Strict Mode. Use bind() directly if you prefer your own effect.

Play from handlers

'use client';
import { useSound } from '@bloxwap/sfx/react';

export function ConfirmButton() {
  const confirm = useSound('success', { volume: 0.8 });
  return <button onClick={() => confirm({ pan: -0.2 })}>Confirm</button>;
}

useSound(name, options?) returns a stable callback while its arguments stay unchanged. Per-call options override the defaults. Audio unlock still requires a real user gesture.

Persist mute and volume

'use client';
import { SoundProvider, useSoundPreference } from '@bloxwap/sfx/react';

function Preferences() {
  const { enabled, volume, setEnabled, setVolume } = useSoundPreference();
  return <>
    <button onClick={() => setEnabled(!enabled)}>{enabled ? 'Mute' : 'Enable sound'}</button>
    <input aria-label="Sound volume" type="range" min={0} max={1} step={0.01}
      value={volume} onChange={event => setVolume(Number(event.target.value))} />
  </>;
}

export function App() {
  return <SoundProvider><Preferences /></SoundProvider>;
}

Preferences use the bloxwap:sfx localStorage key. Consumers and browser tabs stay synchronized; unavailable or invalid storage falls back safely. Volume is clamped to 0–1. The hook can also be used without a provider.

<SoundProvider enabled volume> can override the stored settings while mounted. Omit a prop to use its persisted value. Mount one provider near the root: the audio engine is shared across the app. When the provider unmounts, it restores the saved preference.

Server rendering

Both entry points import safely during SSR. The React entry carries 'use client' for Next.js. Server output and initial hydration use deterministic defaults; persisted preferences load after mount. Audio and binding run in effects or user event handlers.

Edit on GitHub

On this page