DEV Community

Cover image for Improve UX with a sound hook in your Typescript React application
Jakub Patočka
Jakub Patočka

Posted on

Improve UX with a sound hook in your Typescript React application

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');
Enter fullscreen mode Exit fullscreen mode

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'); 
}, []);
Enter fullscreen mode Exit fullscreen mode

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.
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 }
}
Enter fullscreen mode Exit fullscreen mode

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 
});
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
anshul_dashputre_9bd8a03b profile image
Anshul Dashputre

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!