what is making props behavior inconsistent when set as a variable and passed into another function
I am creating a simple audio player using React.
When pushing the "Play" button, audio will be played from a url and the button will display "Pause". Once the audio is finished the button will reset to "Play."
There are three buttons "one" "two" "three". Each one will load a new audio url to be played by the button. Here is the codesandbox.
I'm new to useEffect and useState and I think there is an issue with how I'm updating this with props.
In my audio player component, if I directly set the url (without updating it with props), the audio will play as expected when the "play" button is pushed. However, when I set the url with props
it won't play (even though the console.log()
displays the correct url).
here is my Audio3.js
component (responsible for playing audio:
import React, { useState, useEffect } from "react";
const useAudio = (url) => {
console.log("in 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 = (props) => {
console.log("the current page is", props.currPage)
// const url = "https://github.com/cre8ture/audioFilesForBL/blob/main/2.mp3?raw=true"
const url = "https://github.com/cre8ture/audioFilesForBL/blob/main/" +
props.currPage +
".mp3?raw=true"
console.log("the audio 3 is ", url);
const [playing, toggle] = useAudio(url);
return (
<div>
<button onClick={toggle}>{playing ? "Pause" : "Play"}</button>
</div>
);
};
export default Player;
Here is the threeButtons.js
file:
import React from "react";
function Tabs(props) {
return (
<>
<button onClick={() => props.handleChangeProps(1)}>ONE</button>
<br />
<button onClick={() => props.handleChangeProps(2)}>TWO</button>
<br />
<button onClick={() => props.handleChangeProps(3)}>THRE</button>
</>
);
}
export default Tabs;
Here is the header
component that houses the audio Play
button:
import React from "react";
import Audio from "./Audio3";
function Header(props) {
console.log("header", props.currPage);
return (
<Audio currPage={props.currPage} />
);
}
export default Header;
And lastly, here is my App.js
import React, { useState } from "react";
import Header from "./components/header";
import ConceptTabs from "./components/threeButtons";
function App() {
const [pageID, setPageID] = useState(0);
// goes to ConceptTabs
let handleChange = (id) => {
console.log("clicked", id);
handleChange2(id);
setPageID(id);
return id;
};
// Goes to Audio2
let handleChange2 = (id) => {
console.log("clicked2", id);
return id;
};
console.log("pageID", pageID);
return (
<>
<Header currPage={pageID} />
<br />
<ConceptTabs handleChangeProps={handleChange} />
</>
);
}
export default App;
Thanks so much
Solution 1:
const [audio] = useState(new Audio(url));
is triggered only once when component mounts. If you want to change it when url change, you'll need to have a useEffect
const audio = useRef(new Audio(url));
useEffect(() => {
audio.current = new Audio(url);
// add listeners
return () => {
if (audio.current) {
// Destroy audio & listeners
}
}
}, [url])
You'll also notice that I've used a useRef
instead of useState
for your audio, which is better when you need to store something which do not need a "re-render" when changing value