Reputation: 125
I have written a 25 + 5 clock as a project on freeCodeCamp. It works well on codeine and plays the audio as it should. However, when I try to play it locally on the create-react-app I made the audio doesn't play when timer hits zero. I decided to test out the audio using onClick and it works then. I think it has to do something with the fact that it is being called during useEffect, but I am not sure. Any help is appreciated!
The error that shows up after the timer hits zero is this.
× TypeError: Attempted to assign to readonly property. playSound src/index.js:115
112 | function playSound() {
113 | const audio = document.getElementById("beep");
114 | audio.removeAttribute("readonly");
> 115 | audio.duration = 1; // Need to pass first audio test
| ^ 116 | audio.play();
117 | }
118 | return (
Here is the code for everyone to see.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./index.css";
function Timer() {
const [breakLength, setBreakLength] = useState(5);
const [sessionLength, setSessionLength] = useState(25);
const [timer, setTimer] = useState(1500); // 1500 is 25 minutes, but in seconds
const [timerType, setTimerType] = useState("session");
const [timerState, setTimerState] = useState("stopped");
const [timeoutId, setTimeoutId] = useState("");
React.useEffect(() => {
// If timer is > 0 and the timerState is clicked to running
if (timer > 0 && timerState === "running") {
setTimeoutId(setTimeout(() => setTimer(timer - 1), 1000)); // Must use setTimeout as it only triggers expression once. setInterval will continuously call expression until told otherwise.
}
// If session timer ends.
else if (timer === 0 && timerType === "session") {
setTimerType("break"); // Change timer type back to break
setTimer(breakLength * 60); // Multiply by 60 because we need to convert minutes into seconds
playSound();
}
// If break timer ends
else if (timer === 0 && timerType === "break") {
setTimerType("session"); // Change timer type break
setTimer(sessionLength * 60); // Multiply by 60 because we need to convert minutes into seconds
playSound();
}
clearTimeout(timeoutId);
}, [timer, timerState, timerType, breakLength, sessionLength]);
function resetClick() {
// simply reset all states and audio must be paused then set back to beginning with currentTime = 0
setTimerState("stopped");
setBreakLength(5);
setSessionLength(25);
setTimerType("session");
setTimer(1500);
const audio = document.getElementById("beep");
audio.pause();
audio.currentTime = 0;
}
function decrementBreakClick() {
// Doesn't let break length go below 1 and timer must be stopped
if (breakLength > 1 && timerState === "stopped") {
setBreakLength(breakLength - 1);
}
}
function incrementBreakClick() {
// Doesn't let break length go above 60 and timer must be stopped
if (breakLength < 60 && timerState === "stopped") {
setBreakLength(breakLength + 1);
}
}
function decrementSessionClick() {
// Doesn't let session length go below 1 and timer must be stopped
if (sessionLength > 1 && timerState === "stopped") {
setSessionLength(sessionLength - 1);
setTimer(timer - 60); // Take away 60 as timer is set in seconds.
}
}
function incrementSessionClick() {
// Doesn't let session length go below 1 and timer must be stopped
if (sessionLength < 60 && timerState === "stopped") {
setSessionLength(sessionLength + 1);
setTimer(timer + 60); // Add 60 as timer is set in seconds.
}
}
function startStopClick() {
// if state is stopped then change it to running. Else change running back to stopped
if (timerState === "stopped") {
setTimerState("running");
} else {
setTimerState("stopped");
}
}
// Convert the timer state that is in seconds to minutes and seconds.
function timerToClock() {
let minutes = Math.floor(timer / 60);
let seconds = timer - minutes * 60;
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
return minutes + ":" + seconds;
}
function playSound() {
const audio = document.getElementById("beep");
audio.removeAttribute("readonly");
audio.duration = 1; // Need to pass first audio test
audio.play();
}
return (
<div>
<h1 id="header">25 + 5 Clock</h1>
<div id="machine-container">
<div id="break-session-containter">
<BreakLength
breakLength={breakLength}
decrement={decrementBreakClick}
increment={incrementBreakClick}
/>
<SessionLength
decrement={decrementSessionClick}
increment={incrementSessionClick}
sessionLength={sessionLength}
/>
</div>
<div id="timer-container">
<div id="timer-div">
<h2 id="timer-label">{timerType}</h2>
{/*Calling a function so need to add () */}
<span id="time-left">{timerToClock()}</span>
</div>
</div>
<div id="timer-controls-container">
<button id="start_stop" onClick={startStopClick}>
<i className="fa fa-play"></i>
<i className="fa fa-pause"></i>
</button>
<button id="reset" onClick={resetClick}>
<i className="fa fa-sync"></i>
</button>
<audio
id="beep"
src="https://raw.githubusercontent.com/freeCodeCamp/cdn/master/build/testable-projects-fcc/audio/BeepSound.wav"
></audio>
</div>
<div id="credit-container">
Designed and coded by:
<br />
<a
href="https://codepen.io/your-work/"
target="_blank"
rel="noreferrer"
>
Hunter Lacefield
</a>
</div>
</div>
</div>
);
}
function BreakLength(props) {
return (
<div id="break-length-container">
<h3 id="break-label">Break Length</h3>
<button
id="break-decrement"
className="down-button"
onClick={props.decrement}
>
<i className="fa fa-arrow-down"></i>
</button>
<span id="break-length" className="break-session-number">
{props.breakLength}
</span>
<button
id="break-increment"
className="up-button"
onClick={props.increment}
>
<i className="fa fa-arrow-up"></i>
</button>
</div>
);
}
function SessionLength(props) {
return (
<div id="session-length-container">
<h3 id="session-label">Session Length</h3>
<button
id="session-decrement"
className="down-button"
onClick={props.decrement}
>
<i className="fa fa-arrow-down"></i>
</button>
<span id="session-length" className="break-session-number">
{props.sessionLength}
</span>
<button
id="session-increment"
className="up-button"
onClick={props.increment}
>
<i className="fa fa-arrow-up"></i>
</button>
</div>
);
}
ReactDOM.render(<Timer />, document.getElementById("root"));
Upvotes: 1
Views: 127
Reputation: 4894
I think the problem is because you clearTimeout
as soon you initiate it.
React.useEffect(() => {
let timeoutId;
// If timer is > 0 and the timerState is clicked to running
if (timer > 0 && timerState === "running") {
timeoutId= setTimeout(() => setTimer(timer - 1), 1000);
// Must use setTimeout as it only triggers expression once. setInterval will continuously call expression until told otherwise.
}
// If session timer ends.
else if (timer === 0 && timerType === "session") {
setTimerType("break"); // Change timer type back to break
setTimer(breakLength * 60); // Multiply by 60 because we need to convert minutes into seconds
playSound();
}
// If break timer ends
else if (timer === 0 && timerType === "break") {
setTimerType("session"); // Change timer type break
setTimer(sessionLength * 60); // Multiply by 60 because we need to convert minutes into seconds
playSound();
}
// This must be the clean-up function
() => {
if(timeoutId)
clearTimeout(timeoutId);
}
}, [....]
and if your are using timeoutId
anywhere else, you could additionally set it to the state. If else, don't need a state for it.
Hope that works!
Upvotes: 1