Rethinking web audio feedback with the useSound Hook
Tags: react
Published: 2020-05-27 17:09:48

---
title: Rethinking web audio feedback with the useSound Hook
published: true
date: 2020-05-11 13:00:57 UTC
tags: react
canonical_url: https://blog.logrocket.com/rethinking-audio-feedback-usesound-hook/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/wn4wd8p7sel6xcpni83z.jpeg
---
**Written by [Olasunkanmi John Ajiboye](https://blog.logrocket.com/author/olasunkanmiajiboye/)**✏️

Historically, sounds on the web have gotten a bad rap — and rightly so. They can be jarring, distracting, and sometimes startling to users. However, proper use of sound on an app can provide helpful cues to end users, enriching the user interaction overall.

Sound can be used to highlight specific user actions and accentuate important feedback. When handled elegantly, sound can give life to an otherwise dull user experience. There are many use cases in which sound can enrich user experience. Games and mobile apps may immediately come to mind, but web can also benefit from this enriching user experience.

One golden rule to keep in mind is accessibility, which we will dive into in greater detail later on. A user must have the ability to opt out, and sounds should never auto-play without explicit user consent. With this in mind, the possibilities are endless.

Consider important notifications, new messages in chats when a user has navigated away from the tab or browser, and so on. This is where the [`useSound`](https://github.com/joshwcomeau/use-sound) Hook becomes really useful. It helps to seamlessly integrate sound into your React-based UI.

## Overview

`useSound` is a React Hook that allows you to easily add sound to your React projects. It comes with many options for most of the common use cases. It also extends the howler.js library, which enables you to extend the functionality it already provides.

At ~1KB gzipped, and asynchronously loading about 10KB of howler.js, it is small enough that it won’t significantly impact your app’s performance. According to the [a](https://joshwcomeau.com/react/announcing-use-sound-react-hook/)[n](https://joshwcomeau.com/react/announcing-use-sound-react-hook/)[nouncement blog](https://joshwcomeau.com/react/announcing-use-sound-react-hook/), you get the following functionalities out of the box, and many more:

- Prematurely stop the sound, or pause/resume the sound
- Load an audio sprite and split it up into many individual sounds
- Tweak playback speed to speed up/slow down sounds
- Tons of event listeners
- Lots of other advanced stuff, made possible by howler.js

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Getting started

### Installation

The package can be installed via either `yarn` or `npm`:

```jsx
#  yarn
yarn add use-sound
 # npm
npm install use-sound
```

## Imports

This package exports a single default value: the `useSound` Hook.

```jsx
import useSound from 'use-sound';
```

This is all you need to start using the Hook. Of course, you will need to import the sound to be used as well. With `create-react-app`, you can import this like any other arbitrary file (e.g., an image). You can easily get free sound from resources like [Freesound](https://freesound.org/) or [ZapSplat](https://www.zapsplat.com/).

For example:

```jsx
import ping from '../../sounds/ping.mp3';
const [play, { stop }] = useSound(ping);
```

## Core concepts

As you might have noticed from the imports and usage example above, we destructured `play` and `stop` from the Hook, which accepts the `ping` sound.

These are the two basic methods that can be used for playing and pausing sound. By default, sound doesn’t play until the user interacts with an element or it is intentionally triggered. This is great for accessibility and allows us to lazy-load sound and third-party libraries.

Additionally, the `useSound` Hook can accept the path to the sound directly as the first argument. You can also add a config object consisting of the `hookOptions` for more control and flexibility — for instance, the `playbackRate`, `volume`, `interrupt`, etc. This is reactive and syncs with the state of the component.

```jsx
const [volume, setVolume] = React.useState(0.75);
const [play] = useSound('/path/to/sound', { volume });
```

### `hookOptions`

When calling `useSound`, you can pass it a variety of options referred to as `hookOptions`. The charts below, along with additional details and an exhaustive API list, are available in the `useSound` [API documentation](https://github.com/joshwcomeau/use-sound/blob/master/README.md#api-documentation):

| **Name** | **Value** |
| --- | --- |
| `volume` | Number |
| `playbackRate` | Number |
| `interrupt` | Boolean |
| `soundEnabled` | Boolean |
| `sprite` | `spriteMap` |
| `[delegated]` | — |

Besides the `play` method, you also have access to the `exposedData` object, extending your UI control possibilities:

| **Name** | **Value** |
| --- | --- |
| `stop` | Function – `(id?: string) => void` |
| `pause` | Function – `(id?: string) => void` |
| `isPlaying` | Boolean |
| `duration` | Number (or `null`) |
| `sound` | `Howl` (or `null`) |

### Escape hatches with howler.js

[howler.js](https://howlerjs.com/) is an audio library that makes working with audio in JavaScript easy and reliable across all platforms. Any unrecognized option you pass to `hookOptions` will be delegated to howler.js. You can see the full list of options in the [howler.js docs](https://github.com/goldfire/howler.js#documentation).

Here’s an example of how we can use `onPlayError` to fire a function when there is an error:

```jsx
const [play] = useSound('/beep.mp3', {
  onPlayError: () => {
    console.error('Error occured!');
  },
})
```

Or fire a callback when the sound is muted:

```jsx
const [play] = useSound('/thong.mp3', {
  onmute: () => {
    myCallback()
  },
})
```

We will go into use cases with concrete examples of all the core concepts in the next section.

## Use cases and examples

In this section, we will explore some use cases with code samples and recipes. All examples can be explored or edited directly on [CodeSandbox](https://codesandbox.io/s/use-sound-hjtow).

{% codesandbox hjtow %}

### Popups and notifications

Two of the more common use case scenarios are popups and notifications. Think something similar to a Facebook notification tab; you want to get the user’s attention when they have a new notification, friend request, message, or a like on their posts.

To simulate this scenario, we will build a simple lookalike navbar with notification icons. We will then have a `setInterval` logic that randomly sets notification. I won’t go into the implementation details of the `setInterval`, which is available in full in the CodeSandbox. We will instead focus on handling this particular scenario with `useSound`.

First, create the `AppBar` component. Note that I have also added a checkbox toggle to demonstrate giving the user the ability to permanently turn off or turn on the sound if they so wish. This is important for good user experience and for accessibility.

```jsx
import React, { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBell } from "@fortawesome/free-solid-svg-icons";
import useSound from "use-sound";

import CheckBox from "./CheckBox";
import useInterval from "../hooks/useInterval";
import sound1 from "../assets/sound1.mp3";

const AppBar = () => {
  const [isRunning, setIsRunning] = useState(true);
  const [checked, setChecked] = useState(false);
  const [count, setCount] = useState(0);
  const [play] = useSound(sound1, { volume: 0.2 });

  useInterval(
    () => {
      setCount(count + 1);
      if (checked) {
        play();
      }
    },
    isRunning ? 3000 : null
  );

  const reset = () => {
    setIsRunning(false);
  };

  const toggle = () => {
    setChecked(!checked);
  };

  return (
    <nav className="appbar">
      <div className="toggle">
        <CheckBox handleChange={toggle} checked={checked} />
      </div>
      <span className="notification">
        <FontAwesomeIcon icon={faBell} onClick={() => reset()} />
        {!!count &&amp; <span className="badge">{count}</span>}
      </span>
    </nav>
  );
};
export default AppBar;
```

And the CSS:

```jsx
.appbar {
  display: flex;
  justify-content: space-between;
  background-color: blue;
  align-items: center;
  color: white;
  height: 50px;
}

.toggle {
  margin-left: 5px;
}
.icons * {
  margin: 0 5px;
}
```

First, let’s review what we intend to achieve. We want to keep sounding the notification each _x_ seconds until the user checks the notification. This is useful when a user navigates away from the tab or the browser but we would like to keep their attention.

Here we have simply called the `play()` method for as long as our condition is `true`. To reset or cancel the playing, we simply opt out of playing when `isRunning` or `notification` is `false`.

### Play/pause button

Another common example is playing, pausing, and then resuming sound. Think Spotify or any other audio streaming app. Let’s quickly build this component (the full code is available in the CodeSandbox).

```jsx
import React from "react";
import useSound from "use-sound";

const Pause = ({ stop }) => {
  return (
    <svg className="button" viewBox="0 0 60 60" onClick={()=>stop()}>
      <polygon points="0,0 15,0 15,60 0,60" />
      <polygon points="25,0 40,0 40,60 25,60" />
    </svg>
  );
};

const Play = ({ play }) => {
  return (
    <svg className="button" viewBox="0 0 60 60" onClick={play}>
      <polygon points="0,0 50,30 0,60" />
    </svg>
  );
};

const Player = () => {
  const [play, { stop, isPlaying }] = useSound(sound3);
  return (
    <div className="player">
      {isPlaying ? <Pause stop={stop} /> : <Play play={play} />}
    </div>
  );
};

export default Player;
```

Let’s take a stab at the code above. The `Player` component toggles between `play` and `stop`. Just like with the previous example, we have delegated the `play()` and `stop()` method, respectively, to handle these cases on click.

The other useful piece of data here is the `isPlaying` property. This is a Boolean that tells us whether the sound is currently playing. For this use case, we have employed this property to toggle between play or stop.

### Increasing pitches/volume

Another fun example is increasing pitch or volume.

To demonstrate this, we’d use a simple progress bar. We will increase the length of the progress bar with each click. This example is common in displaying health bars, game status, progress, etc. We will also increase the volume and pitch as the bar grows.

You will notice that the `playbackRate` and `volume` passed to `useSound` are reactive and automatically sync with state. Manipulating any of the `exposedData` is as easy as binding it to a state in the component.

```jsx
import React, { useState } from "react";
import Progress from "react-progressbar";
import useSound from "use-sound";

import sound from "./sound3.mp3";

const ProgressBar = () => {
  const [status, setStatus] = useState(10);
  const [playbackRate, setPlaybackRate] = useState(0.75);
  const [ volume, setVolume]=  useState(0.4);

  const [play] = useSound(sound, {
    playbackRate,
    volume
  });

  const handleIncrease = () => {
    setPlaybackRate(playbackRate => playbackRate + 0.1);
    setStatus(status => status + 10);
    setVolume(volume=>volume+1)
    play();
  };

  return (
    <div>
      <Progress completed={status} onClick={handleIncrease} />
    </div>
  );
};

export default ProgressBar;
```

Again, the full code is available on [CodeSandbox](https://codesandbox.io/s/rising-pitch-iw86h).

### Sprites

Sprites come in handy when we have to deal with a larger number of sounds in our app. Sprites combine many little sound files into one. This decreases files size, and most importantly, it is better for performance as it avoids many parallel HTTP trips to fetch different sound files.

We will build a simple set of buttons and bind the ID to the sound in the sprite such that each button is responsible for playing different sounds in the sprite.

```jsx
import React from "react";
import useSound from "use-sound";

import sound from "./sound3.mp3";


function SpriteDemo() {
  const [play] = useSound(sound, {
    sprite: {
      kick: [0, 350],
      pong: [374, 160],
      bell: [666, 290],
      cowbell: [968, 200]
    }
  });

  const playSound = (e) => {
    e.preventDefault();
    play(e.target.id);
  };

  return (
    <>
      <button id="kick" onClick={e => playSound(e)}>
        Kick
      </button>
      <button id="pong" onClick={e => playSound(e)}>
        Pong
      </button>
      <button id="bell" onClick={e => playSound(e)}>
        Bell
      </button>
      <button id="cowbell" onClick={e => playSound(e)}>
        Cowbell
      </button>
    </>
  );
}
```

There are many more options and possibilities — you are only limited by your creativity. The documentation has more recipes for usage.

## Accessibility concerns

A number of users would disagree that sound enhances UX on the web. And this isn’t just an auditory preference — it can be a cause of serious annoyance and accessibility issues if not handled properly.

Many visually impaired users rely on screen readers to parse the text on the web into sounds, which is then narrated to them. Stuffing the web with confusing sounds could be jarring for them and produce the opposite effect we had in mind. Hence, it is crucial to think critically about sound on the web. There are a few golden rules to keep in mind to ensure wider usability and accessibility.

It is necessary that all users must opt in to sound — that is, the user can decide whether they want to receive sound at all. Users must have the ability to easily mute or stop the sound, and the must be able to permanently disable sound until they decide otherwise. The control to do this should be readily keyboard accessible, e.g., with Tab key.

More importantly, the web application should be 100 percent usable without sound. For users who are hearing impaired, sound would be all but useless; if there is no other way of meaningfully interacting with the site without sound, that renders the website itself useless. In case of longer audio, attempts should be made to provide alternatives, such as a transcript.

The takeaway is to think about _all_ users and the end goal of using sounds in the first place. For instance, in the notification example above, the user can still see the notifications with or without audio; a badge, color change, count, etc. would make that functionality 100 percent usable without audio.

## Conclusion

Audio on the web is under-explored and under-utilized. An elegant, well thought-out use of sound on the web can deeply enrich user experience.

In the above examples, we have barely begun to scratch the surface when it comes to the possibilities. Almost all modern browsers support audio, but the native HTML solution can be hard to configure. The combination of third-party libraries like `useSound` and howler.js, along with some creativity, can produce amazing results.

While keeping accessibility in mind, I would implore product designers and developers to experiment and give audio enhancement a second look. Have a resounding time experimenting.

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [Rethinking web audio feedback with the useSound Hook](https://blog.logrocket.com/rethinking-audio-feedback-usesound-hook/) appeared first on [LogRocket Blog](https://blog.logrocket.com).