I am trying to implement react counter. every 10 seconds I need to update label and progress bar. But in label I could able to display 1 to 6 in 60 seconds successfully. but in timer due to some issue even though it reaches 60 seconds progress bar percange showing 80% only.
timer logic
const [number, setNumber] = useState(0);
const [progBarVal, setProgBarValr] = useState(0);
useEffect(() => {
if (number >= 6) {
return;
}
const intervalID = setTimeout(() => {
setNumber((t) => t + 1);
setProgBarValr((t) => t + 10);
}, 1000);
return () => clearInterval(intervalID);
}, [number, progBarVal]);
with in the return statement
return{
<div > {number}/6 </div>
<Progress done={progBarVal} />
}
progress bar logic
import React, { useState } from 'react';
import './progress.scss';
const Progress = ({ done }) => {
const [style, setStyle] = useState({});
setTimeout(() => {
const newStyle = {
opacity: 1,
width: `${done}%`,
};
setStyle(newStyle);
}, 200);
return (
<div className='met-prog__progress'>
<div className='met-prog__progress-done' style={style}>
{done}%
</div>
</div>
);
};
export default Progress;
I am trying to do if number is 1 the progBarVal 10 like that.
someone help me to understand where it went wrong.
Related
I'm new to React and trying to add some audio to a tenzies game.
The audio is getting weirder every time I click the roll button. And after clicking for a while, the sound is fading away. There is also a warning in the console: 'The AudioContext was not allowed to start.'
I can't find out what causing this issue. Please help!
I don't know how to run react code in StackOverflow. So I'm adding a link to the github repository and live site URL of this game.
Here is the full App.js component's code:
import React from 'react';
import Die from './Components/Die';
import { nanoid } from 'nanoid';
import Confetti from 'react-confetti';
import {Howl} from 'howler';
import winSound from './audio/win.mp3';
import rollSound from './audio/roll.mp3';
import holdSound from './audio/hold.mp3';
export default function App(){
const [dice, setDice] = React.useState(generateNewDice());
const [tenzies, setTenzies] = React.useState(false);
const [audio, setAudio] = React.useState(true);
const [rollCount, setRollCount] = React.useState(0);
const [timer, setTimer] = React.useState(0);
const [timerRunning, setTimerRunning] = React.useState(false);
function holdDieObject(){
return {
value: Math.floor(Math.random()*6) + 1,
isHeld: false,
id: nanoid()
}
}
function generateNewDice(){
let newDice= [];
for(let i=0; i<10; i++){
newDice.push(holdDieObject());
}
return newDice;
}
React.useEffect(()=>{ //Count time per 10 milliseconds when timer is running
let interval;
if(timerRunning){
interval = setInterval(() => {
setTimer((prevTime) => prevTime + 10)
}, 10)
}else{
clearInterval(interval);
}
return () => clearInterval(interval);
},[timerRunning])
React.useEffect(()=>{ //Check all the dice are matched or not
const someDiceHeld = dice.some(die => die.isHeld);
const allDiceHeld = dice.every(die => die.isHeld);
const firstDiceValue = dice[0].value;
const allSameValue = dice.every(die=> die.value === firstDiceValue);
if(someDiceHeld){
setTimerRunning(true);
}
if(allDiceHeld && allSameValue){
setTenzies(true);
// audio && victorySound.play(); // This brings up dependency warning. So moved it to the bottom
setTimerRunning(false)
}
},[dice])
const victorySound = new Howl({
src: [winSound]
})
if(tenzies){
audio && victorySound.play(); // Here
}
const rollDieSound = new Howl({
src: [rollSound]
})
const holdDieSound = new Howl({
src: [holdSound]
})
function holdDice(id){
audio && holdDieSound.play();
setDice(oldDice => oldDice.map(die =>{
return die.id===id ?
{
...die,
isHeld: !die.isHeld
} :
die;
}))
}
function rollDice(){
if(!tenzies){
setDice(oldDice => oldDice.map(die=>{
audio && rollDieSound.play();
return die.isHeld ? die : holdDieObject();
}))
setRollCount(prevCount => prevCount + 1);
}else{
setTenzies(false);
setDice(generateNewDice());
setRollCount(0);
setTimer(0);
}
}
function toggleMute(){
setAudio(prevState => !prevState);
}
function startNewGame(){
setTenzies(false);
setDice(generateNewDice());
}
const minutes = <span>{("0" + Math.floor((timer / 60000) % 60)).slice(-2)}</span>
const seconds = <span>{("0" + Math.floor((timer / 1000) % 60)).slice(-2)}</span>
const milliseconds = <span>{("0" + ((timer / 10) % 100)).slice(-2)}</span>
const dieElements = dice.map((die) => {
return <Die key={die.id}
value={die.value}
isHeld={die.isHeld}
holdDice={()=> holdDice(die.id)}
/>
})
return(
<div>
<main className="board">
<button onClick={toggleMute} className="mute-btn">{audio ? "🔉" : "🔇"}</button>
<h1>Tenzies</h1>
<p>Roll untill the dice are the same. Click each die to freeze it at its current value between rolls.</p>
<div className="die-container">
{dieElements}
</div>
<button onClick={rollDice}>Roll</button>
{tenzies && <div className="scoreboard">
<h2>Congratulations!</h2>
<p className='rollCount'>Rolled: {rollCount}</p>
<p className="rolltime">Time Taken: {minutes}:{seconds}:{milliseconds}</p>
<h3>Your Score: 4500</h3>
<button className='close' onClick={startNewGame}>New Game</button>
</div>}
</main>
{tenzies && <Confetti className="confetti" recycle={false} />}
</div>
)
}
I built a cool little audio player and am having issues with data fetching. The page renders before the audio file in the return statement src, here:
<audio ref={audio} src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg" alt="oops, something went wrong..."></audio>
The NaN shows up in the duration time represented by this line:
{/* duration */}
<div className={styles.duration}>{(duration && !isNaN(duration)) && calculateTime(duration)}</div>
This above line of code isn't preventing the NaN, so I tried my hand at fetching in the useEffect, shown below but that has made this issue worse.
const [data, setData] = useState([])
--------------------
useEffect(() => {
fetch("https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg").then(
res => setData(res.loadedmetadata)
)
})
--------------------
<audio ref={audio} src={data} alt="oops, something went wrong..."></audio>
If anyone could give it a look and point me in the right direction, id be very grateful. Below I will provide all the code for my component.
import React, { useState, useRef, useEffect } from 'react';
import styles from '../styles/AudioPlayer.module.css';
import {BsArrowClockwise} from 'react-icons/bs';
import {BsArrowCounterclockwise} from 'react-icons/bs';
import {BsPlayCircleFill} from 'react-icons/bs';
import {BsPauseCircleFill} from 'react-icons/bs';
const AudioPlayer = () => {
//state
const [isPlaying, setIsPlaying] = useState(false);
const [duration, setDuration] = useState(0);
const [currentTime, setCurrentTime] = useState(0);
const [data, setData] = useState([])
//refs
const audio = useRef();
const progressBar = useRef();
const progressBarAnimation = useRef();
//effects
useEffect(() => {
const seconds = Math.floor(audio.current.duration);
setDuration(seconds);
progressBar.current.max = seconds;
}, [ audio?.current?.loadedmetadata, audio?.current?.readyState ]);
useEffect(() => {
fetch("https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg").then(
res => setData(res.loadedmetadata)
)
})
//functions & Handlers
const calculateTime = (secs) => {
const minutes = Math.floor(secs / 60);
const returnedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
const seconds = Math.floor(secs % 60);
const returnedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
return `${returnedMinutes}:${returnedSeconds}`;
}
const isPlayingHandler = () => {
const prevValue = isPlaying;
setIsPlaying(!prevValue);
if (!prevValue) {
audio.current.play();
progressBarAnimation.current = requestAnimationFrame(whilePlaying);
} else {
audio.current.pause();
cancelAnimationFrame(progressBarAnimation.current);
};
};
const whilePlaying = () => {
progressBar.current.value = audio.current.currentTime;
progressBarValueTicker();
progressBarAnimation.current = requestAnimationFrame(whilePlaying);
};
const progressHandler = () => {
audio.current.currentTime = progressBar.current.value;
progressBarValueTicker();
};
const progressBarValueTicker = () => {
progressBar.current.style.setProperty('--seek-before-width', `${progressBar.current.value / duration * 100}%`);
setCurrentTime(progressBar.current.value);
}
const backwardFifteen = () => {
console.log(progressBar.current.value)
progressBar.current.value = Number(progressBar.current.value) - 15;
console.log(progressBar.current.value)
progressHandler();
};
const forwardFifteen = () => {
console.log(progressBar.current.value)
progressBar.current.value = Number(progressBar.current.value) + 15;
console.log(progressBar.current.value)
progressHandler();
};
return(
<>
<div>
{/* eventually, a loop component tag will replace the below line to loop all audio file title and descriptions*/}
</div>
<div className={styles.audioWrapper}>
{/* eventually, a loop component tag will replace the below line to loop all audio files*/}
<audio ref={audio} src={data} alt="oops, something went wrong..."></audio>
{/* <audio ref={audio} src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg" alt="oops, something went wrong..."></audio> */}
<button className={styles.sideButtons} onClick={backwardFifteen}><BsArrowCounterclockwise />15</button>
<button className={styles.playPauseButton} onClick={isPlayingHandler}>
{ isPlaying ? <BsPauseCircleFill /> : <BsPlayCircleFill /> }</button>
<button className={styles.sideButtons} onClick={forwardFifteen}>15<BsArrowClockwise /></button>
{/* current time */}
<div className={styles.currentTime}>{calculateTime(currentTime)}</div>
{/* progress bar */}
<div>
<input type="range" ref={progressBar} className={styles.progressBar} onChange={progressHandler} defaultValue='0'/>
</div>
{/* duration */}
<div className={styles.duration}>{(duration && !isNaN(duration)) && calculateTime(duration)}</div>
</div>
</>
);
};
export default AudioPlayer;
const onLoadedMetadata = ()=>{
const seconds = Math.floor(audioPlayer.current.duration);
setDuration(seconds);
progressBar.current.max = seconds;
}
<audio ref={audioPlayer} src={audio_file} preload="metadata" onLoadedMetadata={onLoadedMetadata}></audio>
The reason the NaN is rendered is because NaN is a falsy value and will return the value immediately in the expression below.
(duration && !isNaN(duration)) && calculateTime(duration)
// `NaN && !isNaN(NaN)` returns `NaN` because it is falsy
Simply removing the first condition will avoid NaN being rendered.
!isNaN(duration) && calculateTime(duration)
However, the actual duration value will still be NaN and nothing will get rendered. This is because when you check for the audio.current.duration value inside the useEffect the duration hasn't actually updated yet.
To solve this issue, you can listen to the onDurationChange event in the audio element and update the duration state variable when it gets triggered.
// Convert the `useEffect` code into a function instead
const onDurationChangeHandler = (e) => {
const seconds = Math.floor(e.target.duration);
setDuration(seconds);
progressBar.current.max = seconds;
};
<audio
ref={audio}
src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg"
alt="oops, something went wrong..."
onDurationChange={onDurationChangeHandler}
></audio>
So I'm trying to build progress bar and convert minutes to % so I can apply them to the progress below. When the time is 00 then the progress should be 100% filled (or the opposite not filled, doesn't matter)
Here is what I've build until now:
import * as styles from './Screen.module.scss'
import { useState, useEffect } from 'react'
export default function Screen() {
const [countDown, setCountDown] = useState(0);
const [runTimer, setRunTimer] = useState(true);
useEffect(() => {
let timerId;
if (runTimer) {
setCountDown(60 * 5);
timerId = setInterval(() => {
setCountDown((countDown) => countDown - 1);
}, 1000);
} else {
clearInterval(timerId);
}
return () => clearInterval(timerId);
}, [runTimer]);
useEffect(() => {
if (countDown < 0 && runTimer) {
console.log("expired");
setRunTimer(false);
setCountDown(0);
}
}, [countDown, runTimer]);
const seconds = String(countDown % 60).padStart(2, 0);
const minutes = String(Math.floor(countDown / 60)).padStart(2, 0);
const [prg, setPrg] = useState(0);
const fill = {
width: `${prg}%`,
height: '100%',
backgroundColor: 'green',
transform: 'width 0.45s ease-in-out'
}
return (
<main className={styles.container}>
<div className={styles.bar}>
<div className={styles.bar_value}>{minutes}:{seconds}</div>
<div style={fill}></div>
</div>
</main>
)
}
prg shouldn't be state, since it's derived solely from the duration of your countdown and the number of seconds left. (You might want to make 60 * 5 a constant, a prop, or state though.)
Simply:
const prg = (countDown / (60 * 5)) * 100;
will have your progress bar count down from 100% to 0%.
To have it count up,
const prg = 100 - (countDown / (60 * 5)) * 100;
I'm new to reactjs and and trying to make this simple stopwatch. this code is work in my browser, however when I minimize the browser the time paused, and only continue when I open the browser, do you found something I must be missed? Thanks in advance!
import { useState, useEffect } from "react";
export const SW = () => {
const [mSec, setMSec] = useState(0);
const [sec, setSec] = useState(0);
const [min, setMin] = useState(0);
const [isOn, setIsOn] = useState(false);
const start = () => setIsOn(true);
const stop = () => setIsOn(false);
const reset = () => {
setIsOn(false);
setMin(0);
setSec(0);
setMSec(0);
};
useEffect(() => {
let ms;
if (isOn) {
ms = setInterval(() => setMSec((mSec) => mSec + 1), 10);
if (sec === 59) {
setSec(0);
setMin((min) => min + 1);
}
if (mSec === 99) {
setMSec(0);
setSec((sec) => sec + 1);
}
}
return () => {
clearInterval(ms);
};
}, [mSec, sec, isOn]);
return (
<div>
<p>
{min.toString().padStart(2, "0")}:{sec.toString().padStart(2, "0")}:
{mSec.toString().padStart(2, "0")}
</p>
{!isOn && <button onClick={start}>{!mSec ? "start" : "resume"}</button>}
{isOn && <button onClick={stop}>stop</button>}
<button disabled={!mSec} onClick={reset}>
reset
</button>
</div>
);
};
It's not an issue on useEffect or your code, simply it's how browsers work, they execute JavaScript on the active tabs.
The solution is to use Web Workers API to execute JavaScript in the background.
For more details:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
https://www.w3schools.com/html/html5_webworkers.asp
How can I show countdown timer in minute and seconds. right now I am able to show the timer in seconds, only but I want to display both minutes and seconds both.
Currently my countdown timer is showing in this way Countdown: 112 but I want it to be like Countdown: 1: 52
import React from "react";
export default function App() {
const [counter, setCounter] = React.useState(120);
React.useEffect(() => {
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter === 0 ? "Time over" : counter}</div>
</div>
);
}
Here's a complete solution with formatting time:
// Prepend `0` for one digit numbers. For that the number has to be
// converted to string, as numbers don't have length method
const padTime = time => {
return String(time).length === 1 ? `0${time}` : `${time}`;
};
const format = time => {
// Convert seconds into minutes and take the whole part
const minutes = Math.floor(time / 60);
// Get the seconds left after converting minutes
const seconds = time % 60;
//Return combined values as string in format mm:ss
return `${minutes}:${padTime(seconds)}`;
};
export default function App() {
const [counter, setCounter] = React.useState(120);
React.useEffect(() => {
let timer;
if (counter > 0) {
timer = setTimeout(() => setCounter(c => c - 1), 1000);
}
return () => {
if (timer) {
clearTimeout(timer);
}
};
}, [counter]);
return (
<div className="App">
{counter === 0 ? "Time over" : <div>Countdown: {format(counter)}</div>}
</div>
);
}
A few notes about your original code:
Since the next value of counter depends on the previous one it's better to use the functional form of setState.
It's a good practice to clear timeout when component unmounts.
Import hooks from import stage:
Hooks react
import React, { useState, useEffect } from "react";
export default function App() {
const [counter, setCounter] = useState(120);
useEffect(() => {
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter === 0 ? "Time over" : counter}</div>
</div>
);
}
Changing your setCounter method to following should work.
React.useEffect(() => {
counter !== 'Time Over' && setTimeout(() => setCounter(counter > 1 ? counter - 1 : 'Time Over'), 1000);
}, [counter]);
This can be done like so:
import React from "react";
export default function App() {
const [counter, setCounter] = React.useState(120);
React.useEffect(() => {
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter === 0 ? "Time over" : counter}</div>
</div>
);
}