I'd simplify state to a single boolean: your audio is either playing or not, so there's no need for redundancy here. let isPlaying = false;
isn't state, so it's not persistent across renders and doesn't do anything pauseToggle
doesn't already achieve.
In fact, audio elements have a .paused
property that makes the separate state boolean redundant, but for React rendering/lifecycle purposes the extra boolean is handy.
Since the audio is a side effect to the rendering, I'd make it a ref, then trigger play/pause functions based on when playing
changes in a useEffect
.
A bit of a bonus, but it's probably worth handling the onended
event to set playing to false when the track ends, for starters.
const {useEffect, useRef, useState} = React;
const MusicPlayer = ({url}) => {
const [playing, setPlaying] = useState(false);
const audioRef = useRef(new Audio(url));
useEffect(() => {
const handler = () => setPlaying(false);
audioRef.current.addEventListener("ended", handler);
return () =>
audioRef.current.removeEventListener("ended", handler)
;
}, [audioRef.current]);
useEffect(() => {
audioRef.current[playing ? "play" : "pause"]();
}, [playing]);
return (
<div>
<button onClick={() => setPlaying(!playing)}>
{playing ? "=" : ">"}
</button>
</div>
);
};
const url = "https://upload.wikimedia.org/wikipedia/en/a/a9/Webern_-_Sehr_langsam.ogg";
ReactDOM.createRoot(document.querySelector("#app"))
.render(<MusicPlayer url={url}/>);
button {
font-size: 2em;
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>
Putting the audio into a hook helps make it reusable and keeps your parent component clean:
const {useEffect, useRef, useState} = React;
const useAudio = url => {
const [playing, setPlaying] = useState(false);
const audioRef = useRef(new Audio(url));
useEffect(() => {
const handler = () => setPlaying(false);
audioRef.current.addEventListener("ended", handler);
return () =>
audioRef.current.removeEventListener("ended", handler)
;
}, [audioRef.current]);
useEffect(() => {
audioRef.current[playing ? "play" : "pause"]();
}, [playing]);
return [playing, setPlaying];
};
const MusicPlayer = ({url}) => {
const [playing, setPlaying] = useAudio(url);
return (
<div>
<button onClick={() => setPlaying(!playing)}>
{playing ? "=" : ">"}
</button>
</div>
);
};
const url = "https://upload.wikimedia.org/wikipedia/en/a/a9/Webern_-_Sehr_langsam.ogg";
ReactDOM.createRoot(document.querySelector("#app"))
.render(<MusicPlayer url={url} />);
button {
font-size: 2em;
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>