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"));
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!
Related
I want to set minutes:seconds timer for my customer when they start tracker that tracker specific set for that customer if new customer start tracker then that timer start from begin and old customer tracker continue with that timer :
Example :
Customer A -> Click on button timer start -> 00:01 then 00:02 then 00:03 continue ..
Customer B -> Client on button of timer start -> 00.01
On GUI display :
Customer A = 00.03 and continue timer increase ..
Customer B = 00.01 and continue timer increase ..
`
const minutescallId = ( callId ) => {
const caller_id = callId;
const secObj = {
[caller_id] : 61
}
var callId = callId;
var secs = parseInt( 0 );
var mins = parseInt( 0 );
// sec[callId] = parseInt( 61 );
//var sec[callId];
console.log("START");
console.log(secObj[callId]);
console.log("END");
const minutes_array = localStorage.getItem( "callminutesinfo" );
durationInterval = setInterval( () => {
// console.log("Seconds" + secs);
// console.log("minutes" + mins);
if ( secObj[caller_id] < 60 ) {
secObj[caller_id] = parseInt( secObj[caller_id] + 1 );
// setSeconds( { [callId]: secs } );
//setSeconds({ ...seconds, [callId]: secs });
// const newarray = [ minutes_array, { callId: callId, secs: secs, mins: mins } ];
// setMinutescalls( minutes_array => [ ...minutes_array, { callId: callId, secs: secs, mins: mins } ] );
//localStorage.setItem( "callminutesinfo", newarray )
setSecondcalls({...secondcalls,[callId]: secObj[caller_id]});
console.log("Coming seconds start");
console.log(secObj[caller_id]);
console.log(mins);
console.log("Coming seconds END");
setMinutescalls({...minutescalls,[callId]: mins});
}
else{
secObj[caller_id] = parseInt( secs + 1 );
// setSeconds( { [callId]: secs } );
//setSeconds({ ...seconds, [callId]: secs });
// const newarray = [ minutes_array, { callId: callId, secs: secs, mins: mins } ];
// setMinutescalls( minutes_array => [ ...minutes_array, { callId: callId, secs: secs, mins: mins } ] );
//localStorage.setItem( "callminutesinfo", newarray )
setSecondcalls({...secondcalls,[callId]: secObj[caller_id]});
console.log("Coming seconds start");
console.log(secObj[caller_id]);
console.log(mins);
console.log("Coming seconds END");
setMinutescalls({...minutescalls,[callId]: mins});
}
if ( secs[callId] >= 59 ) {
secs[callId] = parseInt( 0 );
mins[callId] = parseInt( mins[callId] + 1 );
// setSeconds( { callId: secs } );
// setMinutes( { callId: mins } );
// const newarray = [ minutes_array, { callId: callId, secs: secs, mins: mins } ];
// setMinutescalls( minutes_array => [ ...minutes_array, { callId: callId, secs: secs, mins: mins } ] );
// localStorage.setItem( "callminutesinfo", newarray )
console.log("Coming minutes start");
console.log(secs[callId]);
console.log(mins[callId]);
console.log("Coming minutes END");
setSecondcalls({...secondcalls,[callId]: secs[callId]});
setMinutescalls({...minutescalls,[callId]: mins[callId]});
}
}, 1000 );
};
<div className="drag_column">
<div className="drag_row">
{calls.map( ( task ) => (
<div className={( ( displayMinutes.indexOf( task.callId, task.callId ) > -1 ) ? 'display-block' : 'display-none' )}>
<Typography className={"text-black"}> {minutescalls[task.callId] < 10 ? `0${minutescalls[task.callId]}` : minutescalls[task.callId]} : {secondcalls[task.callId] < 10 ? `0${secondcalls[task.callId]}` : secondcalls[task.callId]} </Typography>
</div>
<div className={( ( incomingReceive.indexOf( task.callId, task.callId ) > -1 ) ? 'display-block' : 'display-none' )}>
<Typography style={{ display: receiveCallBtn === true ? 'block' : 'none' }} className={"mr-1 mb-0 " + ( ( receiveCallBtn === true ) ? 'action-button-display-add' : 'action-button-display-none' )} onClick={() => minutescallId( task.callId )}><img src={CallPickup} width="25px" alignItems="end" style={{
marginLeft: "140px", display: "flex", position: "absolute",
top: "30px", cursor: "pointer"
}} alt="CallReceive" /></Typography>
</div>
</div>
</div>`
Please let me know where i can have problem to manage different minutes seconds as per callId of calls.
I have an array with objects sorted with a date entry and a component to list in what approximated time it was listed.
const TimeHeader = ({date}) => {
const calculateDays = (date) => {
let timeDiff = new Date().getTime() - new Date(date).getTime();
let days = Math.ceil(timeDiff / (1000 * 3600 * 24));
return days;
}
let daysGone = calculateDays(date);
let color = "green";
let text = "Less than 1 Week";
if (daysGone > 7 && daysGone <= 30){
color = "blue";
text = "Less than 1 Month";
} else if (daysGone > 30 && daysGone <= 90){
color = "yellow";
text = "Less than 3 Months";
} else if (daysGone > 90){
color = "red";
text = "More than 3 Months";
}
return (
<div style={{backgroundColor: color}}>{text}</div>
)
}
How would I call upon this component to be rendered only once for each instance? (Once for Less than 1 week, once for less than one month etc)
Right now I am just calling it before each item listing but of course it leads a lot of repetition.
{list.map(item => {
return (
<div key={item.id}>
<TimeHeader date={item.date} />
<ItemDisplay item={item} />
</div>
)
})}
One solution would be to split the array to different categories beforehand, but I'm wondering if there is a nice solution that doesn't require splitting the array.
Im trying to do a Pomodoro Clock timer, which is basically two timers that alternate between.
Thats all the code:
import React, { useState } from "react";
function Challenge20() {
const [timer, setTimer] = useState('');
let minutes = 0;
let seconds = 0;
const [workRest, setWorkRest] = useState('work');
function startTimer() {
document.getElementById('start').style.display = 'none';
minutes = document.getElementById('work').value - 1;
seconds = 59;
setInterval(reduceSeconds, 1000);
};
function reduceSeconds() {
if (seconds < 10) {
setTimer(minutes + ':' + '0' + seconds);
}
else {
setTimer(minutes + ':' + seconds);
}
seconds -= 1;
if (seconds < 1 && minutes > 0) {
seconds = 59;
minutes -= 1;
}
else if (seconds == 0 && minutes == 0){
setWorkRest(workRest == 'work' ? 'rest' : 'work');
minutes = document.getElementById(workRest == 'work' ? 'work' : 'rest').value;
}
};
return (
<>
<label>Work Minutes:</label>
<input id='work' type='number' max='60'/>
<br/>
<label>Rest Minutes:</label>
<input id='rest' type='number' max='60'/>
<br/>
<br/>
<span id='timer'>{workRest} -> {timer}</span>
<button id='start' onClick={() => startTimer()}>Start!</button>
</>
);
};
export default Challenge20;
The problem is in this part:
else if (seconds == 0 && minutes == 0){
setWorkRest(workRest == 'work' ? 'rest' : 'work');
minutes = document.getElementById(workRest == 'work' ? 'work' : 'rest').value;
}
The setState is not changing from 'work' to 'rest', also tried to call a function to change the state, clearing interval and 2 separated if, nothing worked, what am I doing wrong?
useState is not work inside the condition. For ex: you are set the state value in the if condition. State value not updated in condition.
I think this is what you're trying to achieve? The problem is that the timer keeps going. On the next iteration, it sets workRest back to its previous value. To solve this, I used clearInterval to stop iterating, and decremented seconds to display 00:00 on the timer. As such, I had to assign the interval creation to a variable we can pass into clearInterval.
import React, { useState } from "react";
function Challenge20() {
const [timer, setTimer] = useState("");
let minutes = 0;
let seconds = 0;
const [workRest, setWorkRest] = useState("work");
let interval;
function startTimer() {
document.getElementById("start").style.display = "none";
minutes = document.getElementById("work").value - 1;
seconds = 59;
interval = setInterval(reduceSeconds, 1);
}
function reduceSeconds() {
if (seconds < 10) {
setTimer(minutes + ":" + "0" + seconds);
} else {
setTimer(minutes + ":" + seconds);
}
seconds -= 1;
if (seconds < 1 && minutes > 0) {
seconds = 59;
minutes -= 1;
} else if (seconds == 0 && minutes == 0) {
console.log();
setWorkRest(workRest == "work" ? "rest" : "work");
minutes = document.getElementById(workRest == "work" ? "work" : "rest")
.value;
clearInterval(interval);
seconds -= 1;
}
}
return (
<>
<label>Work Minutes:</label>
<input id="work" type="number" max="60" />
<br />
<label>Rest Minutes:</label>
<input id="rest" type="number" max="60" />
<br />
<br />
<span id="timer">
{workRest} -> {timer}
</span>
<button id="start" onClick={() => startTimer()}>
Start!
</button>
</>
);
}
for (var k = 0; k < 10; k++) {
if (k % 2 === 0) {
weatherText = <div className="in_break">
}
weatherText += <div className="eachD" key={k}>
<div>
{
countIt === 0 ? (currDate.getHours() > 12 ? "Tonight" : "Today") : dayOfWeek[weekDay]
}
</div>
<div>
{
getDate
}
</div>
<div>
{
<ReturnIcon />
}
</div>
</div>
if (k % 2 === 0) {
weatherText += </div>
}
}
What I am looking to do is group all the eachD by two inside the `in_break' div
But I keep getting:
Parsing error: Unexpected token 'weatherText = </div>'
This is the layout:
in_break
eachD
eachD
in_break
eachD
eachD
in_break
eachD
eachD
...
Please help me resolve my issue
UPDATED
I hope this find it's way to your demand:
setWeatherTextItems = (countId, currDate, dayOfWeek, weekDay, getDate) => {
// you make sure all the variables such like countId and currDate are available inside this function.
const items = [];
for (var k = 0; k < 10; k++) {
items.push(
<div className="eachD" key={k}>
<div>
{countIt === 0
? currDate.getHours() > 12
? "Tonight"
: "Today"
: dayOfWeek[weekDay]}
</div>
<div>{getDate}</div>
<div>{<ReturnIcon />}</div>
</div>
);
}
return items;
}
renderInBreak = () => {
const items = this.setWeatherTextItems();
const inBreakItems = [];
let breakBlock = [];
let newBreak = false;
items.forEach((textItem, index) => { //1
if(!newBreak) {
breakBlock.push(textItem);
if(index + 1 === items.length){
inBreakItems.push(breakBlock);
}
} else {
inBreakItems.push(breakBlock);
breakBlock = [];
breakBlock.push(textItem);
//without this condition check, the last element will be left out of an odd array length
if(index + 1 === items.length) {
inBreakItems.push(breakBlock)
}
}
if(index % 2) newBreak = true; //false
else newBreak = false; //false
});
return inBreakItems.map(twoTextWeatherItems => (
<div className="in_break">
{twoTextWeatherItems}
</div>
))
}
render(){
<div>
{this.renderInBreak()}
</div>
}
OLD
React is supposed to handle things differently, maybe this will work:
Define a method in your component that will set your items:
setWeatherTextItems = (countId, currDate, dayOfWeek, weekDay, getDate) => {
// you make sure all the variables such like countId and currDate are available inside this function.
const items = [];
for (var k = 0; k < 10; k++) {
items.push(
<div className="eachD" key={k}>
<div>
{countIt === 0
? currDate.getHours() > 12
? "Tonight"
: "Today"
: dayOfWeek[weekDay]}
</div>
<div>{getDate}</div>
<div>{<ReturnIcon />}</div>
</div>
);
}
return items;
}
in your render method, or where you are willing to render these items:
render(){
<div className="in_break">{this.setWeatherTextItems()}</div>
}
Read more about how to render things in a loop.
You can add the conditions you want inside the for loop, or where it makes sense to you.
Not sure if the logic would work in a react environment but as far as I can see from your plain code when you are going to add the 'in_break' div aren't you just assigning the whole whetherText again instead of joining text to it?
Shouldn't this:
if (k % 2 === 0) {
weatherText = </div>
}
be written like this?
if (k % 2 === 0) {
weatherText += </div>
}
Edit following the typo correction:
I tried to run your code on codepen to have a quicker and easier understanding on how to find a solution.
I created an helper function with your code then I returned
<div className="Container" dangerouslySetInnerHTML={{__html: weatherText}}></div>
This enables you to have the result you are looking for. Only the even elements have the 'in_break' class.
Hope this helped and let me know if this is not correct.
Codepen: https://codepen.io/dpgian/pen/EBzRmX
I have a Vue component that works just fine. Now I'm trying to convert that code to ReactJS equivalent. My attempt on React
var ticksArray = Array.apply(null, {length: 27}).map(Number.call, Number);
export default class Timer extends Component {
constructor(props) {
super(props);
this.state = {
angle:250,
minangle:0,
maxangle:270,
xDirection:"",
yDirection:"",
oldX:0,
dragging: false
}
}
onMousedown(){
this.setState({dragging : true});
}
onMouseup(){
this.setState({dragging : false});
}
onMousemove(e){
if(!this.state.dragging)
return;
this.setState({
xDirection : this.state.oldX < e.pageX ? 'right' : 'left',
oldX:e.pageX,
yDirection: this.state.xDirection === 'left' ? 'down' : 'up'
});
if(this.state.yDirection === 'up' && this.state.angle + 2 <=
this.state.maxangle)
this.setState({angle:this.state.angle += 2})
else if(this.state.yDirection === 'down' && this.state.angle - 2 >=
this.state.minangle)
this.setState({angle:this.state.angle -= 2})
}
knobStyle(){
return {
'transform':'rotate('+this.state.angle+'deg)'
}
}
activeTicks(){
return (Math.round(this.state.angle / 10) + 1);
}
currentValue(){
return Math.round((this.state.angle/270)*100) + '%'
}
componentDidMount(){
document.addEventListener('mouseup',this.state.onMouseup)
document.addEventListener('mousemove',this.state.onMousemove)
}
render() {
var tickDivs = ticksArray.map(function(item) {
return (
<div key={item} className="tick"></div>
);
});
return (
<div id="timer">
<div className="knob-surround">
<div className="knob"></div>
<span className="min">Min</span>
<span className="max">Max</span>
<div className="ticks" className="n <= activeTicks ?
'activetick' : ''">
{tickDivs}
</div>
</div>
</div>
);
}
}
It's not working. I'm missing something. I'm assuming the problem lies in this code bit.
<div className="ticks" className="n <= activeTicks ?
'activetick' : ''">
Please help fix this.
Add this here instead of comment:
React uses the following syntax:
className={n <= activeTicks ? 'activetick' : ''}
In componentDidMount you assign handlers in a wrong way, should be like:
document.addEventListener('mouseup', this.onMouseup)
Note here that handler is not a part of your state. And the corresponding definition of the handler:
private onMouseup = () => {...}
The reason to store reference for the event handler instead of having class method - see in #3
Do not forget to unsubscribe your event handlers in componentWillUnmount like this:
window.removeEventListener("mouseup", this.onMouseup);
UPDATE:
Here is an example working without using arrow functions: https://jsfiddle.net/6dnrLw4n/4/