I am trying to add a lapTime to a totalLaptime state using Reactjs.
I have started by putting time in to milliseconds i.e 81300 which equates to 01:21:300 (1 minute 21 seconds 300 milliseconds). I then have a button which onClick adds a new lapTime and then i add lapTime to totalLapTime.
Lastly, i then have a function that takes the total milliseconds and converts it to a readable format i.e 01:21:300.
I have my state:
this.state = {
lap: 0,
lapTime: 0,
totalLapTimes: 0,
totalLapTimeCalc: ''
};
My click function to move on to the next lap
handleClick = () => {
this.setState(({lap, tyres, deg}) => ({
lap: lap + 1,
lapTime: this.randomLap(), <-- function makes a random milliseconds
totalLapTimes: + this.state.lapTime + this.state.totalLapTimes,
totalLapTimeCalc: this.lapTimeCalc(this.state.totalLapTimes)
}));
};
The function to convert the time from milliseconds to readable format:
lapTimeCalc = (ms) => {
return new Date(ms).toISOString().slice(11, -1);
};
Expected result should be 01:21:xxx added to totalLapTimeCalc after each click on the lap button.
At the moment, when i click the lap button i have to click it 3 times before the totalLapTime is even converted, and by then the totalLapTimeCalc is incorrect.
You are using state that hasn't been set yet to calculate the state you're setting.
If you change your click handler a little bit, it will work fine:
class App extends React.Component {
constructor() {
super();
this.state = {
lap: 0,
lapTime: 0,
totalLapTimes: 0,
totalLapTimeCalc: ''
};
}
handleClick = () => {
const { lap, totalLapTimes } = this.state;
const lapTime = this.randomLap();
const totalTime = totalLapTimes + lapTime;
const totalCalc = this.lapTimeCalc(totalTime)
this.setState({
lap: lap + 1,
lapTime,
totalLapTimes: totalTime,
totalLapTimeCalc: totalCalc,
});
};
lapTimeCalc = (ms) => {
return new Date(ms).toISOString().slice(11, -1);
};
randomLap = () => Math.floor(Math.random() * 100000) + 80000;
render() {
const { lap, lapTime, totalLapTimes, totalLapTimeCalc } = this.state;
return (
<div>
<button onClick={this.handleClick}>Click Me</button>
<p>Lap:{lap}</p>
<p>Lap Time:{lapTime} ({this.lapTimeCalc(lapTime)})</p>
<p>Total Lap Time:{totalLapTimes}</p>
<p>Total Lap Time Calc:{totalLapTimeCalc}</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Related
I broke my Code down to make it look simple
const [factor, setfactor] = useState(1);
const [nullify, setNullify] = useState(1);
const Price = 10;
const Bonus = 15;
const finalPrice = (Price * factor - Bonus) * nullify;
// start 5 = (10 * 2 -15)* 1
// after Click -5 = (10 * 1 -15)* 1
//what i want 0 = (10 * 1 -15)* 0
const handleFactor = () => {
setfactor(1)
validate()
};
const validate = () => {
if (finalPrice <= 0) {
setNullify(0);
}
};
useEffect(() => {
handleFactor();
}, [factor]);
//HTML Stuff
return (
<>
<input type="checkbox" onClick={handleFactor} />
<input type="checkbox" onClick="activate bonus" />
{finalPrice}
</>
);
I want, if the bonus make it below 0 the final price should not be a minus number , instead it should becomes a zero itself - but it doesn't work.
I know, that the final price will be 5 when the validation do it's thing. But how do I get the newPrice?
State updates are asynchronous, so you can get the updated state on the next render. When you do this
const handleFactor = () => {
setfactor(1)
validate()
};
the state haven't updated yet, and validate would use the previous (valid) values. You'll need to move validate to the next render for this to work.
However, you don't need another state to ensure that finalPrice is above 0. Use Math.max(price * factor - bonus, 0)to get the max between the final price and0` (or any other minimum number):
const { useState, Fragment } = React;
const Demo = ({ price, bonus }) => {
const [factor, setfactor] = useState(2);
const finalPrice = Math.max(price * factor - bonus, 0);
const handleFactor = () => {
setfactor(1)
};
return (
<Fragment>
<input type="checkbox" onClick={handleFactor} />
{finalPrice}
</Fragment>
);
};
ReactDOM
.createRoot(root)
.render(<Demo price={10} bonus={15} />);
<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="root"></div>
I have four counters that I would like to animate (incrementing the count from 0 to a specific number) using JavaScript. My code is the following:
const allCounters = document.querySelectorAll('.counterClass');
counters.forEach(allCounters => {
const updateCounter = () => {
const end = +allCounters.getAttribute('data-target');
const count = +allCounters.innerText;
const increment = end / 200;
if (count < end) {
allCounters.innerText = count + increment;
setTimeout(updateCounter, 1);
} else {
allCounters.innerText = end;
}
};
updateCounter();
});
In React, I wasn't sure how to get it to run. I tried including the code after the using dangerouslySetInnerHTML, but that's not working. (I'm new to React).
I appreciate any assistance you could give me. Thanks so much!
Right before I posted my question, I found a plug-in (https://github.com/glennreyes/react-countup) that could do it, but wondered if it's still possible using JS. Thanks!
While using React, try to avoid direct DOM operations (both query and modifications). Instead, let React do the DOM job:
const Counter = ({start, end}) = {
// useState maintains the value of a state across
// renders and correctly handles its changes
const {count, setCount} = React.useState(start);
// useMemo only executes the callback when some dependency changes
const increment = React.useMemo(() => end/200, [end]);
// The logic of your counter
// Return a callback to "unsubscribe" the timer (clear it)
const doIncrement = () => {
if(count < end) {
const timer = setTimeout(
() => setCount(
count < (end - increment)
? count + increment
: end
),
1);
return () => clearTimeout(timer);
}
}
// useEffect only executes the callback once and when some dependency changes
React.useEffect(doIncrement, [count, end, increment]);
// Render the counter's DOM
return (
<div>{count}</div>
)
}
const App = (props) => {
// Generate example values:
// - Generate 5 counters
// - All of them start at 0
// - Each one ends at it's index * 5 + 10
// - useMemo only executes the callback once and
// when numCounters changes (here, never)
const numCounters = 5;
const countersExample = React.useMemo(
() => new Array(numCounters)
.fill(0)
.map( (c, index) => ({
start: 0,
end: index*5 + 10,
})
),
[numCounters]
);
return (
<div id="counters-container">
{
// Map generated values to React elements
countersExample
.map( (counter, index) => <Counter key={index} {...counter}/> )
}
</div>
)
}
I have a switch and a timer. The switch just changes values between 0 and 1 and the timer is an interval every 5 seconds. When the interval is hit, the switch value changes (if it was 0 now it's 1, if it was 1 now it's 0) and I also have a button which forces the switch value to change and resets the timer.
I also have another timer which is running at 5ms interval and that's just to display how much time is left on the 5 second interval.
Now this is where I'm stuck. I have a button which is supposed to increment that time left by two seconds. So if three seconds have passed since the last switch, and I hit increment, now we're at six seconds until the next switch..
And that part has just been confusing me on how I should go about doing it.
Here's my html and javascript code
window.addEventListener('DOMContentLoaded', (event) => {
let timer = document.getElementById("timer");
let switchvalue = document.getElementById("switchvalue");
let force = document.getElementById("force");
let increment = document.getElementById("increment");
let boolvalue = false;
let maxtime = 5000;
var tick = Date.now();
var switchinterval = setInterval(timecounter,maxtime);
function update(){
tick = Date.now();
boolvalue = !boolvalue;
switchvalue.textContent = boolvalue;
clearInterval(switchinterval);
switchinterval = setInterval(timecounter,maxtime);
}
function timecounter(){
update();
}
let displayinterval = setInterval(() => {
let now = Date.now();
let elapsed = now-tick;
timer.textContent = maxtime-elapsed;
}, 5);
force.addEventListener("click",e=>{
update();
});
increment.addEventListener("click",e=>{
//What do I do here?
});
});
<html>
<head>
<script src="timer.js"></script>
</head>
<body>
<div id="timer">10</div>
<div id="switchvalue">false</div>
<button id="force">Switch</button>
<button id="increment">Increment</button>
</body>
</html>
I'm not sure how to make that increment button function. It seems like a simple enough problem to solve..but my brains not working
If I understood your question correctly, your increment button should simply add two seconds, right?
increment.onclick =e=>
{
tick += 2000 // add 2s
}
const timer = document.getElementById("timer")
, switchvalue = document.getElementById("switchvalue")
, force = document.getElementById("force")
, increment = document.getElementById("increment")
;
let boolvalue = false
, maxtime = 5000
, tick = Date.now()
, switchinterval
, displayinterval
;
switchinterval = setInterval(timecounter, maxtime)
;
function update()
{
tick = Date.now()
boolvalue = !boolvalue
switchvalue.textContent = boolvalue
clearInterval(switchinterval)
switchinterval = setInterval(timecounter, maxtime)
}
function timecounter()
{
update()
}
displayinterval = setInterval(_=>
{
let now = Date.now()
, elapsed = now - tick
timer.textContent = maxtime - elapsed
}, 5)
force.onclick =e=>
{
update()
}
increment.onclick =e=>
{
tick += 2000 // add 2s
}
<div id="timer">?</div>
<div id="switchvalue">false</div>
<button id="force">Switch</button>
<button id="increment">Increment</button>
But you forgot to take into account that the delay indicated in setInterval is not reliable, it represents just a hypothetical delay according to the availability of the system, most often (always) it is late.
If you want to have a reliable delay, you must use the value of the system clock to check the elapsed delay.
Which is precisely the case study of your exercise, and which has the merit of using only one setInterval to do everything:
const timer = document.getElementById("timer")
, switchvalue = document.getElementById("switchvalue")
, force = document.getElementById("force")
, increment = document.getElementById("increment")
;
let boolvalue = false
, maxtime = 5000
, timeTarget = Date.now() + maxtime
, interval_5ms
;
const timeSwich =_=>
{
switchvalue.textContent = boolvalue = !boolvalue
timeTarget = Date.now() + maxtime
}
interval_5ms = setInterval(_=>
{
let tim = timeTarget - Date.now()
if ( tim<=0 )
{
timeSwich()
tim = maxtime
}
timer.textContent = tim
}, 5)
force.onclick = timeSwich
increment.onclick =_=>
{
timeTarget = Math.min((timeTarget +2000), Date.now() + maxtime)
// timeTarget += 2000 // add 2s
}
<div id="timer">?</div>
<div id="switchvalue">false</div>
<button id="force">Switch</button>
<button id="increment">Increment</button>
I am making a simple Typescript counter to track my win percentage in my Legends of Runeterra games I play. I can't figure out why when I increment a win or a loss I get NaN as my win percentage. The logic seems fine (obviously you can't decrement right now, that's a problem for later), I just want to focus on fixing the NaN error for now.
Here's my counter component:
import React, { useState } from 'react'
// add a ? after the type name if you want any one of these to be optional, ex: wins?
const Counter: React.FC<{
initialGamesPlayed: number
initialWins: number
initialLosses: number
initialWinPercentage: number
initialDeckName: string
}> = ({
initialDeckName,
initialWinPercentage,
initialWins,
initialLosses,
initialGamesPlayed,
}) => {
const [deckName, setDeckName] = useState(initialDeckName)
const [wins, setWins] = useState(initialWins)
const [losses, setLosses] = useState(initialLosses)
const [totalGames, setTotalGames] = useState(initialGamesPlayed)
const [winPercentage, setWinPercentage] = useState(initialWinPercentage)
const incrementWins = () => {
setWins(wins + 1)
winPercentageCalc()
console.log(winPercentage)
}
const decrementWins = () => {
if (wins > 0) setWins(wins - 1)
winPercentageCalc()
}
const incrementLosses = () => {
setLosses(losses + 1)
winPercentageCalc()
console.log(winPercentage)
}
const decrementLosses = () => {
if (losses > 0) setLosses(losses - 1)
winPercentageCalc()
}
const winPercentageCalc = () => {
setTotalGames(wins + losses)
setWinPercentage((wins / totalGames) * 100)
}
return (
<div>
<p>Deck Name: </p>
<p>wins: {wins} </p>
<button onClick={incrementWins}>+</button>
<button onClick={decrementWins}>-</button>
<p>losses: {losses}</p>
<button onClick={incrementLosses}>+</button>
<button onClick={decrementLosses}>-</button>
<p>Win Percentage: {winPercentage} % </p>
</div>
)
}
export default Counter
Thanks for taking a look!
The setWins, setLosses, setTotalGames and setWinPercentage are all asynchronous functions. So the first time your call winPercentageCalc, this is what happens:
const winPercentageCalc = () => {
setTotalGames(wins + losses) // This is asynchronous, so...
setWinPercentage((wins / totalGames) * 100) // totalGames = 0 => NaN
}
When you divide wins by totalGames, totalGames has not been updated so you divide by 0 which gives NaN (Not a Number) as a result
I'm attempting to build a countdown timer in React. My understanding was that componentDidMount will be called immediately after render, and so I can use it to call setState with the current time after a one second delay. Like so:
componentDidMount() {
setTimeout(this.setState({ now: this.getTime() }), 1000)
}
However, while componentDidMount is being called (I checked with console.log), the state is not updating. How can I get componentDidMount to update the state and thus re-render the component with a new time?
Here is the full class:
class Timer extends React.Component {
constructor() {
super();
this.state = {
now: this.getTime(),
end: this.getTime() + 180
}
}
getTime() {
return (Date.now()/1000)
}
formatTime() {
let remaining = this.state.end - this.state.now
let rawMinutes = (remaining / 60) | 0
let rawSeconds = (remaining % 60) | 0
let minutes = rawMinutes < 10 ? "0" + rawMinutes : rawMinutes
let seconds = rawSeconds < 10 ? "0" + rawSeconds : rawSeconds
let time = minutes + ":" + seconds
return time
}
componentDidMount() {
setTimeout(this.setState({ now: this.getTime() }), 1000)
}
render() {
return(
<div id="countdown">
{ this.formatTime() }
</div>
)
}
}
first parameter of setTimeout is function - what you are passing is not a function, but its return value
to make this work you could wrap your setState with anonymous function like this:
setTimeout(() => this.setState({ now: this.getTime() }), 1000)