See how to implement a sound hook into your React Typescript application.
Subsequent samples are written in TypeScript (v5), Preact (v10) and Vite (v4), but the API notation is identical to React (v18).
Introduction
I’ve always been passionate about web development and decided to challenge myself with browser game development. Creating an engaging and interactive game required attention to many details, one of which was the implementation of sound in the game. This led me to create my own React/Preact audio hook called useAudio
. It’s nothing difficult, but it’s not something a developer would deal with on a daily basis.
How it will be used
I knew I would need something where I could pass a list of audio files and play them one at a time. For example, I want to make a clinking sound when clicked on a button and whistle when there is an error. I imagined something like:
// Simple initialization of sound to individual events.
const sound = useAudio({ map: SFX_MAP });
const handleClick = () => sound.play('click');
const handleError = () => sound.play('error');
I knew that I would also want to implement background music and I would not want to write another hook for it. So I imagined what other settings I might want to pass to the hook:
// This is how I would like to initialize the background music.
const music = useAudio({
map: MUSIC_MAP,
volume: 0.25,
loop: true
});
// Play on mount. Do not care about unmount.
useEffect(() => {
music.play('ambient');
}, []);
Writing a hook
I started by creating types. UseAudioMap
for the sound map and UseAudoOptions
for setting the hook.
// Audio map type.
type UseAudioMap = Record<string, HTMLAudioElement>;
// Audio settings interface.
interface UseAudoOptions {
map: UseAudioMap, // Map of audio files.
volume?: number, // Audio volume (0-1).
loop?: boolean // Play once/infinite times.
}
Then I created a map of the sounds that I would like to use in the application in the given situations.
// Map of SFX audio files.
const SFX_MAP = {
click : new Audio('./click.mp3'),
error: new Audio('./error.mp3'),
} satisfies UseAudioMap;
// Map of background music audio files.
const MUSIC_MAP = {
ambient : new Audio('./ambient-music.mp3'),
dramatic: new Audio('./dramatic-atmo.mp3'),
} satisfies UseAudioMap;
Next, I wrote a concrete implementation of a simple hook for sound that solves at least basic use-cases.
/**
* Creates controllable sound instance.
* @returns { play, pause } functions
*/
const useAudio = ({ map, volume = 1, loop = false }: UseAudoOptions) => {
const sound = useRef<HTMLAudioElement>();
// Stop audio on unmount.
useEffect(() => {
return () => { pause(); }
}, []);
// Play sound per key.
const play = (key: keyof typeof map) => {
pause();
sound.current = map[key];
sound.current.load(); // reinitializes audio
sound.current.volume = volume;
sound.current.loop = loop;
sound.current.play();
}
// Stop current audio.
const pause = () => {
sound.current?.pause();
}
// Return controls.
return { play, pause }
}
Now I can use the hook exactly as I imagined at the beginning.
const sound = useAudio({ map: SFX_MAP });
const music = useAudio({
map: MUSIC_MAP,
volume: 0.25,
loop: true
});
Conclusion
Adding sound changed the game. Users said it was more immersive and fun. Although this is not a programmingly complex solution and could certainly be tweaked endlessly, I thought it was interesting to share the idea that the UI can make sounds on the web as well :).
Top comments (1)
This is a cool concept! Using a custom React hook to manage sound effects and background music in a TypeScript application sounds really useful. The breakdown of the code and examples makes it easy to understand.
I especially like the separation of sound effects and background music with volume and loop options. It would be interesting to see how this hook could be extended to handle more complex sound scenarios like playlists or dynamic sound changes.
Overall, a great example of how to enhance UX with sound in a web application!