DEV Community

Cover image for The Only React Hook for Sound Effects You Will Ever Need
bricourse
bricourse

Posted on

The Only React Hook for Sound Effects You Will Ever Need

**useSound **A React Hook for Sound Effects

The web needs more (tasteful) sounds!

  • đź‘‚ Lets your website communicate using 2 human senses instead of 1

  • 🔥 Declarative Hooks API

  • ⚡️ <1kb bytes (gzip) in your bundle! ~10kb loaded async.

  • ✨ Built with Typescript

  • đź—Ł Uses a powerful, battle-tested audio utility: Howler.js

Installation

Package can be added using yarn:

yarn add use-sound
Enter fullscreen mode Exit fullscreen mode

Or, use NPM:

npm install use-sound
Enter fullscreen mode Exit fullscreen mode

UMD build available on unpkg.

Demo

The tutorial includes many demos, as well as instructions for finding and preparing sound effects. It’s a great place to start.

You can also view the storybook, which includes lots of quick examples.

Get the Book: Collection of React Hooks PDF

Play sound on click

import useSound from 'use-sound';

import boopSfx from '../../sounds/boop.mp3';

const BoopButton = () => {
  const [play] = useSound(boopSfx);

  return <button onClick={play}>Boop!</button>;
};
Enter fullscreen mode Exit fullscreen mode

Playing on hover

This demo only plays the sound while hovering over an element. The sound pauses when the mouse leaves the element:

NOTE: Many browsers disable sounds until the user has clicked somewhere on the page. If you’re not hearing anything with this example, try clicking anywhere and trying again.

import useSound from 'use-sound';

import fanfareSfx from '../../sounds/fanfare.mp3';

const FanfareButton = () => {
  const [play, { stop }] = useSound(fanfareSfx);

  return (
    <button onMouseEnter={play} onMouseLeave={stop}>
      <span role="img" aria-label="trumpet">
        🎺
      </span>
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

Increase pitch on every click

With the playbackRate option, you can change the speed/pitch of the sample. This example plays a sound and makes it 10% faster each time:

import useSound from 'use-sound';

import glugSfx from '../../sounds/glug.mp3';

export const RisingPitch = () => {
  const [playbackRate, setPlaybackRate] = React.useState(0.75);

  const [play] = useSound(glugSfx, {
    playbackRate,
    // `interrupt` ensures that if the sound starts again before it's
    // ended, it will truncate it. Otherwise, the sound can overlap.
    interrupt: true,
  });

  const handleClick = () => {
    setPlaybackRate(playbackRate + 0.1);
    play();
  };

  return (
    <Button onClick={handleClick}>
      <span role="img" aria-label="Person with lines near mouth">
        đź—Ł
      </span>
    </Button>
  );
};
Enter fullscreen mode Exit fullscreen mode

API Documentation

The useSound hook takes two arguments:

  • A URL to the sound that it wil load

  • A config object (HookOptions)

It produces an array with two values:

  • A function you can call to trigger the sound

  • An object with additional data and controls (ExposedData)

When calling the function to play the sound, you can pass it a set of options (PlayOptions).

Let’s go through each of these in turn.

HookOptions

When calling useSound, you can pass it a variety of options:

NameValuevolumenumberplaybackRatenumberinterruptbooleansoundEnabledbooleanspriteSpriteMap[delegated] —

  • volume is a number from 0 to 1, where 1 is full volume and 0 is comletely muted.

  • playbackRate is a number from 0.5 to 4. It can be used to slow down or speed up the sample. Like a turntable, changes to speed also affect pitch.

  • interrupt specifies whether or not the sound should be able to "overlap" if the play function is called again before the sound has ended.

  • soundEnabled allows you to pass a value (typically from context or redux or something) to mute all sounds. Note that this can be overridden in the PlayOptions, see below

  • sprite allows you to use a single useSound hook for multiple sound effects. See “Sprites” below.

[delegated] refers to the fact that any additional argument you pass in HookOptions will be forwarded to the Howl constructor. See "Escape hatches" below for more information.

The play function

When calling the hook, you get back a play function as the first item in the tuple:

const [play] = useSound('/meow.mp3');
//      ^ What we're talking about
Enter fullscreen mode Exit fullscreen mode

You can call this function without any arguments when you want to trigger the sound. You can also call it with a PlayOptions object:

NameValueidstringforceSoundEnabledbooleanplaybackRatenumber

  • id is used for sprite identification. See “Sprites” below.

  • forceSoundEnabled allows you to override the soundEnabled boolean passed to HookOptions. You generally never want to do this. The only exception I've found: triggering a sound on the "Mute" button.

  • playbackRate is another way you can set a new playback rate, same as in HookOptions. In general you should prefer to do it through HookOptions, this is an escape hatch.

ExposedData

The hook produces a tuple with 2 options, the play function and an ExposedData object:

const [play, exposedData] = useSound('/meow.mp3');
//                ^ What we're talking about
Enter fullscreen mode Exit fullscreen mode

NameValuestopfunction ((id?: string) => void)pausefunction ((id?: string) => void)isPlayingbooleandurationnumber (or null)soundHowl (or null)

  • stop is a function you can use to pre-emptively halt the sound.

  • pause is like stop, except it can be resumed from the same point. Unless you know you'll want to resume, you should use stop; pause hogs resources, since it expects to be resumed at some point.

  • isPlaying lets you know whether this sound is currently playing or not. When the sound reaches the end, or it's interrupted with stop or paused, this value will flip back to false. You can use this to show some UI only while the sound is playing.

  • duration is the length of the sample, in milliseconds. It will be null until the sample has been loaded. Note that for sprites, it's the length of the entire file.

  • sound is an escape hatch. It grants you access to the underlying Howl instance. See the Howler documentation to learn more about how to use it. Note that this will be null for the first few moments after the component mounts.

Advanced

Sprites

An audio sprite is a single audio file that holds multiple samples. Instead of loading many individual sounds, you can load a single file and slice it up into multiple sections which can be triggered independently.

There can be a performance benefit to this, since it’s less parallel network requests, but it can also be worth doing this if a single component needs multiple samples. See the Drum Machine story for an example.

For sprites, we’ll need to define a SpriteMap. It looks like this:

const spriteMap = {
  laser: [0, 300],
  explosion: [1000, 300],
  meow: [2000, 75],
};
Enter fullscreen mode Exit fullscreen mode

SpriteMap is an object. The keys are the ids for individual sounds. The value is a tuple (array of fixed length) with 2 items:

  • The starting time of the sample, in milliseconds, counted from the very beginning of the sample

  • The length of the sample, in milliseconds.

This visualization might make it clearer:

We can pass our SpriteMap as one of our HookOptions:

const [play] = useSound('/path/to/sprite.mp3', {
  sprite: {
    laser: [0, 300],
    explosion: [1000, 300],
    meow: [2000, 75],
  },
});
Enter fullscreen mode Exit fullscreen mode

To play a specific sprite, we’ll pass its id when calling the play function:

<button
  onClick={() => play({id: 'laser'})}
>
Enter fullscreen mode Exit fullscreen mode

Escape hatches

Howler is a very powerful library, and we’ve only exposed a tiny slice of what it can do in useSound. We expose two escape hatches to give you more control.

First, any unrecognized option you pass to HookOptions will be delegated to Howl. You can see the full list of options in the Howler docs. Here's an example of how we can use onend to fire a function when our sound stops playing:

const [play] = useSound('/thing.mp3', {
  onend: () => {
    console.info('Sound ended!');
  },
});
Enter fullscreen mode Exit fullscreen mode

If you need more control, you should be able to use the sound object directly, which is an instance of Howler.

For example: Howler exposes a fade method, which lets you fade a sound in or out. You can call this method directly on the sound object:

const Arcade = () => {
  const [play, { sound }] = useSound('/win-theme.mp3');

  return (
    <button
      onClick={() => {
        // You win! Fade in the victory theme
        sound.fade(0, 1, 1000);
      }}
    >
      Click to win
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

Additional Resources for learning React:

React Hooks Video Player

React — The Complete Guide (incl Hooks, React Router, Redux)

Github: https://github.com/joshwcomeau/use-sound

Top comments (0)