Playing Sound in React.js: Solutions and Best Practices

Nov 25, 2025 · Programming · 11 views · 7.8

Keywords: React.js | Audio | Hooks | useSound | JavaScript | Web Development

Abstract: This article provides a comprehensive guide on handling audio playback in React.js, covering common issues like 'this' binding errors, solutions using ES6 class properties, React Hooks, and the use-sound library. It includes advanced topics on managing multiple players, code examples, and accessibility considerations for robust audio implementation.

Introduction

In modern web development, integrating audio playback into React applications can enhance user experience, but it often comes with challenges such as handling state and event bindings. This article addresses a common issue where developers encounter errors like "Uncaught TypeError: Cannot read property 'setState' of undefined" when attempting to play sound in React components. We will explore the root causes and provide comprehensive solutions using modern React features.

Common Problem: 'this' Binding in React Components

The original code in the question uses class methods for play and pause without proper binding, leading to the 'this' context being lost when the methods are called as event handlers. In JavaScript, the value of 'this' depends on how a function is called, and in React, event handlers can lose the component instance context if not bound correctly.

class Music extends React.Component { constructor(props) { super(props); this.state = { play: false, pause: true }; this.url = "http://streaming.tdiradio.com:8000/house.mp3"; this.audio = new Audio(this.url); // Missing binding for play and pause methods } play() { this.setState({ play: true, pause: false }); // 'this' might be undefined this.audio.play(); } pause() { this.setState({ play: false, pause: true }); this.audio.pause(); } render() { return ( <div> <button onClick={this.play}>Play</button> <button onClick={this.pause}>Pause</button> </div> ); } }

In this code, the play and pause methods are not bound in the constructor or using arrow functions, so when onClick is triggered, 'this' is undefined, causing the error.

Solution 1: Using ES6 Class Properties

To avoid binding issues, we can use ES6 class property syntax with arrow functions, which automatically bind 'this' to the class instance.

class Music extends React.Component { state = { play: false }; audio = new Audio(this.props.url); // Assuming url is passed as prop componentDidMount() { this.audio.addEventListener('ended', () => this.setState({ play: false })); } componentWillUnmount() { this.audio.removeEventListener('ended', () => this.setState({ play: false })); } togglePlay = () => { this.setState(prevState => ({ play: !prevState.play }), () => { this.state.play ? this.audio.play() : this.audio.pause(); }); } render() { return ( <div> <button onClick={this.togglePlay}>{this.state.play ? 'Pause' : 'Play'}</button> </div> ); } }

This approach simplifies the code by eliminating the need for explicit binding and combines play and pause into a single toggle function.

Solution 2: Using React Hooks

With the introduction of Hooks in React 16.8, functional components can manage state and side effects. Here's a custom hook for audio playback.

import React, { useState, useEffect } from "react"; const useAudio = (url) => { const [audio] = useState(new Audio(url)); const [playing, setPlaying] = useState(false); const toggle = () => setPlaying(!playing); useEffect(() => { playing ? audio.play() : audio.pause(); }, [playing]); useEffect(() => { audio.addEventListener('ended', () => setPlaying(false)); return () => { audio.removeEventListener('ended', () => setPlaying(false)); }; }, []); return [playing, toggle]; }; const Player = ({ url }) => { const [playing, toggle] = useAudio(url); return ( <div> <button onClick={toggle}>{playing ? "Pause" : "Play"}</button> </div> ); };

This hook encapsulates the audio logic, making it reusable and easier to test.

Managing Multiple Audio Players

For applications requiring multiple audio sources, we can extend the hook to handle concurrent playback, ensuring only one audio plays at a time.

import React, { useState, useEffect } from 'react'; const useMultiAudio = (urls) => { const [sources] = useState( urls.map(url => ({ url, audio: new Audio(url) })) ); const [players, setPlayers] = useState( urls.map(() => ({ playing: false })) ); const toggle = (targetIndex) => () => { setPlayers(prevPlayers => { const newPlayers = [...prevPlayers]; const currentIndex = prevPlayers.findIndex(p => p.playing); if (currentIndex !== -1 && currentIndex !== targetIndex) { newPlayers[currentIndex].playing = false; newPlayers[targetIndex].playing = true; } else if (currentIndex !== -1) { newPlayers[targetIndex].playing = false; } else { newPlayers[targetIndex].playing = true; } return newPlayers; }); }; useEffect(() => { sources.forEach((source, i) => { players[i].playing ? source.audio.play() : source.audio.pause(); }); }, [players]); useEffect(() => { sources.forEach((source, i) => { source.audio.addEventListener('ended', () => { setPlayers(prevPlayers => { const newPlayers = [...prevPlayers]; newPlayers[i].playing = false; return newPlayers; }); }); }); return () => { sources.forEach(source => { source.audio.removeEventListener('ended', () => {}); }); }; }, []); return [players, toggle]; }; const MultiPlayer = ({ urls }) => { const [players, toggle] = useMultiAudio(urls); return ( <div> {players.map((player, i) => ( <div key={i}> <button onClick={toggle(i)}>{player.playing ? 'Pause' : 'Play'}</button> </div> ))} </div> ); };

This implementation manages an array of audio players and ensures that playing one pauses others.

Leveraging the use-sound Hook

For more advanced audio features, the use-sound library provides a convenient hook built on Howler.js, offering benefits like audio sprites and volume control.

import useSound from 'use-sound'; import boopSfx from '../../sounds/boop.mp3'; const BoopButton = () => { const [play] = useSound(boopSfx); return <button onClick={play}>Boop!</button>; };

use-sound handles many edge cases, such as loading and playback, and supports features like interrupting sounds and changing playback rate. It is ideal for sound effects in interactive UIs.

Best Practices and Accessibility

When adding sound to web applications, consider accessibility: provide mute controls, avoid autoplay, and ensure critical information is not conveyed solely through audio. Tools like use-sound make it easier to implement these practices.

Conclusion

Playing sound in React.js can be straightforward with proper state management and event handling. By using ES6 class properties, Hooks, or libraries like use-sound, developers can create robust audio features while maintaining code quality and user experience.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.