React Class Component - This.Set.State - reactjs

I'm trying to complete this countdown timer using a class component in REACT. I believe I'm just missing "this.Setstate" adding . Can you please review and let me know where that would be added in this component? Thanks in advance! (I've edited the code by adding "this.Setstate", however, I'm receiving an error message that it's not a function). Just to add more detail, I'm trying to have the countdown timer start when the screen loads. It's not triggered by a button or anything.
import React,{Component} from "react";
class Countdown extends Component{
constructor(props){
super(props);
this.state={
days:30,
hours:720,
minutes:59,
seconds:59
}
}
componentDidMount(){
let countDownDate = new Date("Jul 30, 2022 11:00:00").getTime();
/*Function myFunc runs every second*/
let myfunc = setInterval(function() {
let now = new Date().getTime();
let timeleft = countDownDate - now;
this.setState({
days: Math.floor(timeleft / (1000 * 60 * 60 * 24)),
hours : Math.floor((timeleft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
minutes: Math.floor((timeleft % (1000 * 60 * 60)) / (1000 * 60)),
seconds: Math.floor((timeleft % (1000 * 60)) / 1000),
})
} , 1000);
}
render(){
return (
<>
<div>
<p>{this.days}</p>
<p>{this.hours}</p>
<p>{this.minutes}</p>
<p>{this.seconds}</p>
</div>
</>
)
}
}
export default Countdown;

To save the values in the state, as you mentioned, you effectively need to add the this.setState method.
You are currently storing the date values into global values like this.days. This works but it doesn't re-render the component on change.
So what you need to do is replace the this.<DATE_VALUE> with something like this:
/*Breakdown the calculation*/
this.setState({
days: YOUR_DAYS_VALUES,
hours: YOUR_HOURS_VALUES,
minutes: YOUR_MINUTES_VALUES,
seconds: YOUR_SECONDS_VALUES,
})
Edit:
As #madQuestions commented, as this.setState is inside a setInterval function we need to bound it to the current class (this is not necessary with arrow functions). This happens because inside the setInterval scope we don't have a clue of the class values. That is why we need the binding.
You can check this comment post to understand better what is happening.
So your interval would look something like this:
let myfunc = setInterval(
function () {
let now = new Date().getTime();
let timeleft = countDownDate - now;
this.setState({
days: Math.floor(timeleft / (1000 * 60 * 60 * 24)),
hours: Math.floor(
(timeleft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
),
minutes: Math.floor((timeleft % (1000 * 60 * 60)) / (1000 * 60)),
seconds: Math.floor((timeleft % (1000 * 60)) / 1000)
});
}.bind(this), // we added the binding here
1000
);
Additionally I was able to see in your code that you are still returning the this.<DATE_VALUE> in your JSX code. You have to change that to this.state.<DATE_VALUE> to get the state values.

Related

React Intl with relativeTime formatting

I'm working on a kind of dynamic timestamp for messages using Intl.
I want the timestamps to be dynamic in the way that it automatically transitions from ".. seconds ago" to "... minutes ago" to "... hours ago" to "today", after which it'll just return the date it's been posted. I know there's the <RelativeFormat> component, but I want to use the API instead.
The API has a method called intl.relativeFormat, but can't seem to figure out how to use it...
I'm a junior programmer so it's all still a bit new to me 😅😅
I appreciate your time :)
If you need more info, please let me know. I'll try to provide you with more.
Thanks!
Documentation for the RelativeFormat function can be found here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat.
The idea is that you create an instance of relative time format function, with some pre-defined settings that you want the output to follow. For example, you can set your relative time format function to return English strings in a shortened format.
const rtf = new Intl.RelativeTimeFormat('en', { style: 'narrow' });
console.log(rtf.format(3, 'quarters'));
//expected output: "in 3 qtrs."
You also need to pass negative values in order to get labels intended for the past.
const rtf = new Intl.RelativeTimeFormat('en', { style: 'narrow' });
console.log(rtf.format(-3, 'quarters'));
//expected output: "3 qtrs. ago"
The next part leverages an answer given by #fearofawhackplanet here on StackOverflow
//The 'timestamp' function parameter is your timestamp passed in milliseconds.
function timeDifference(timestamp, locale) {
const msPerMinute = 60 * 1000;
const msPerHour = msPerMinute * 60;
const msPerDay = msPerHour * 24;
const msPerMonth = msPerDay * 30;
const msPerYear = msPerDay * 365;
const current = Date.now();
const elapsed = current - timestamp;
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
if (elapsed < msPerMinute) {
return rtf.format(-Math.floor(elapsed/1000), 'seconds');
}
else if (elapsed < msPerHour) {
return rtf.format(-Math.floor(elapsed/msPerMinute), 'minutes');
}
else if (elapsed < msPerDay) {
return rtf.format(-Math.floor(elapsed/msPerHour), 'hours');
}
else {
return new Date(timestamp).toLocaleDateString(locale);
}
}
//
// code to test the above function
//
const fifteenSecondsAgo = new Date();
const tenMinutesAgo = new Date();
const twoHoursAgo = new Date();
fifteenSecondsAgo.setSeconds(fifteenSecondsAgo.getSeconds() - 15);
tenMinutesAgo.setMinutes(tenMinutesAgo.getMinutes() - 10);
twoHoursAgo.setHours(twoHoursAgo.getHours() - 2);
console.log(timeDifference(fifteenSecondsAgo.getTime(), 'en'));
console.log(timeDifference(fifteenSecondsAgo.getTime(), 'es'));
console.log(timeDifference(tenMinutesAgo.getTime(), 'en'));
console.log(timeDifference(tenMinutesAgo.getTime(), 'es'));
console.log(timeDifference(twoHoursAgo.getTime(), 'en'));
console.log(timeDifference(twoHoursAgo.getTime(), 'es'));
Here is a JSFiddle link to see the code running - https://jsfiddle.net/mhzya237/1/
Here is a similar idea, it also deals with future/present/past times.
function getRelativeTime(time) {
const now = new Date();
const diff = Math.abs(time - now);
const mark = (time - now) >> -1 || 1;
if (diff === 0) return new Intl.RelativeTimeFormat('en').format(0,"second");
const times = [
{ type: 'second', seconds: 1000 },
{ type: 'minute', seconds: 60 * 1000 },
{ type: 'hour', seconds: 60 * 60 * 1000 },
{ type: 'day', seconds: 24 * 60 * 60 * 1000 },
{ type: 'week', seconds: 7 * 24 * 60 * 60 * 1000 },
{ type: 'month', seconds: 30 * 24 * 60 * 60 * 1000 },
{ type: 'year', seconds: 12 * 30 * 24 * 60 * 60 * 1000 },
];
let params = [];
for (let t of times) {
const segment = Math.round(diff / t.seconds);
if (segment >= 0 && segment < 10) {
params = [(segment * mark) | 0, t.type];
break;
}
}
return new Intl.RelativeTimeFormat('en').format(...params);
}
const time = getRelativeTime(new Date(new Date().getTime() - 2 * 1000));
console.info('relative time is', time);
The function takes a time param, finds the seconds difference relative to now, uses the array map to calculate which type yields the closest match and uses it as a param for Intl.RelativeTimeFormat
You can improve getRelativeTime(time) function by either returning the params array and call Intl.RelativeTimeFormat from outside the function or also pass the locale (and options) to the function.
I'm sure there are smarter ways to get rid of the times array, perhaps by creating a wrapping closure but it will force you to "initialize" this utility function first

setInterval in a promise, only executes once in React

I'm trying to create a countdown timer with one second ticking away each second. The interval works only one time and does not execute. The problem is that I do not have this.state.timetill until a server call is made, then I can begin the timer. Therefore, the setInterval function has to be tucked away in a promise, where it executes only one. The timetill variable however can be a constant, once retrieved it does not change.
componentDidMount = async () => {
const that = this;
*do stuff*
this.someFunction().finally(() => {
/*This right here, below is not working */
setInterval(that.createTimer(), 1000);
});
}
My JSX is such
{this.state.setTimer}
function in question, the time has to be divided by 1000 since javascript uses miliseconds
createTimer = async timerNow => {
var timerNow = (await new Date().getTime()) / 1000;
// Find the distance between now and the count down date
var distance = this.state.timeTill - timerNow;
// Time calculations for days, hours, minutes and seconds
var days = Math.floor(distance / (1000 * 60 * 60 * 24));
var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
//the variable displayed
var setTimer = days + "d " + hours + "h " + minutes + "m " + seconds + "s ";
this.setState({setTimer});
};
You pass the returned value of createTimer to setInterval, instead pass the function but don't invoke it
setInterval(this.createTimer, 1000)
There's a few things you should do:
1. Have a method that will actually call setState
2. Change setInterval to call a method reference
3. Cleanup any pending request on unmounting:
4. [optional] you can drop that
private createTimer = { ... }
private setTimer = () => {
this.setState({ timer: this.createTimer() });
}
private _mounted: boolean = false;
private _intervalHandler: number | null = null;
componentDidMount = async () => {
this._mounted = true;
this.someFunction().finally(() => {
if (!this._mounted) { return; }
this._intervalHandler = setInterval(this.setTimer, 1000);
});
}
componentWillUnmoun = () => {
this._mounted = false;
if (this._intervalHandler !== null) {
clearInterval(this._intervalHandler);
}
}
This will ensure you are not performing state changes after the component was unmounted.

React Stopwatch timer not working as expected

Need a React Timer Stopwatch that starts from 00:00:00 i.e., (hh:mm:ss) that estimates the time taken to finish a quiz, tried various things storing things in state.
Something similar to this :
But, setState Makes the app crash.
The Logic :
var seconds = 0, minutes = 0, hours = 0, t;
state = { clock : '' };
componentDidMount()
{
timer();
}
add() {
seconds++;
if (seconds >= 60) {
seconds = 0;
minutes++;
if (minutes >= 60) {
minutes = 0;
hours++;
}
}
time = (hours ? (hours > 9 ? hours : "0" + hours) : "00") + ":" + (minutes ? (minutes > 9 ? minutes : "0" + minutes) : "00") + ":" + (seconds > 9 ? seconds : "0" + seconds);
this.setState({ clock : time });
timer();
}
timer() {
t = setTimeout(add, 1000);
}
/* Start button */
start.onclick = timer;
/* Stop button */
stop.onclick = function() {
clearTimeout(t);
}
The View :
</div>
{this.state.clock}
<div>
Any way how to create a Stopwatch with start and stop option in react?
Thanks in advance..!

Using RxJS's `generate` to create a non-drifiting timer, how to use the `scheduler`?

In search for a timer - triggered every minute, on the minute - that doesn't drift, I found this solution. However I'm having difficulties understanding how to use / construct the last parameter scheduler?: SchedulerLike.
const noDrift = generate(
0,
_ => true, // start condition
i => ++i, // iterate
i => i, // result selector
i => ... // scheduler?: SchedulerLike; every minute on the minute, but how?
);
noDrift.subscribe(() => {
// action
});
original solution that drifts:
const date = new Date();
// calculate how many ms are left till the full minute is reached
const tillNextFullMinute = (60 - date.getSeconds()) * 1000 - date.getMilliseconds();
// start on the next full minute, then just every minute
this.currentTime = timer(tillNextFullMinute, 60 * 1000);
this.currentTime.subscribe(value => {
// action
});

calling another method inside same view

I making a simple game that uses a two minute JavaScript timer. I can get the javascript timer to work without using backbone. The code is at the bottom for the working timer, and here's a fiddle of it http://jsfiddle.net/mjmitche/hDRjR/19/
However, once I try to the timer code into a few different methods inside a Backbone view, I'm getting an error depending on how a key method, displayTime, code is defined
Steps:
1) I create a new clockView and save it to a variable clock_view
var clock_view = new ClockView({ model: game});
Inside the initializer of clockview, I set up these variables that are used by the timer code
var totalWait = 120;
var secondsRemaining = totalWait;
var hasFocus = true;
var hasJustFailed = false;
The startClock method gets triggered from elsewhere
this.model.bind("gameStartedEvent", this.startClock, this);
startClock uses setInterval to call displayTime method every second. Depending on how displayTime is coded [a) displayTime(), b) this.displayTime(), c) clock_view.displayTime() ], displayTime triggers a different error.
startClock: function(){
console.log("start clock");
setInterval(function(){
this.secondsRemaining -= 1;
console.log("working");
displayTime(); //uncaught reference error: displayTime is not defined
this.displayTime(); //Uncaught TypeError: Object [object Window] has no method 'displayTime'
clock_view.displayTime();// `display time gets called but triggers NAN`
if(secondsRemaining == 0) $('#timer').fadeOut(1000);
}, 1000);
},
If displayTime is called from setInterval as displayTime() it says it's not defined. If I do this.displayTime(), I get a object window has no method. If I call it clock_view.displayTime(), it triggers a NAN error, which I think may be caused because the way the variables are defined in the initializer
displayTime is defined directly below startClock like this
displayTime: function () {
var minutes = Math.floor(secondsRemaining / 60);
var seconds = secondsRemaining - (minutes * 60);
if (seconds < 10) seconds = "0" + seconds;
var time = minutes + ":" + seconds;
$('#timer').html(time);
},
Update
This is a fiddle of the whole ClockView in a Backbone format, although it doesn't work because it's missing other parts of the program (such as the model that triggers the event). I'm including it only to make the question more readable
http://jsfiddle.net/mjmitche/RRXnK/85/
Original working clock code http://jsfiddle.net/mjmitche/hDRjR/19/
var displayTime = function () {
var minutes = Math.floor(secondsRemaining / 60);
var seconds = secondsRemaining - (minutes * 60);
if (seconds < 10) seconds = "0" + seconds;
var time = minutes + ":" + seconds;
$('#timer').html(time);
};
$('#timer').css('marginTop', 0);
setInterval(function(){
secondsRemaining -= 1;
displayTime();
if(secondsRemaining == 0) $('#timer').fadeOut(1000);
}, 1000);
This should work. The main points are, how variables are accessed inside the View.
HTML:
<div id="view">
<div id="timer">
<span class="time">2:00</span>
</div>
<div id="options">
<input type="button" class="action_button" value="New Game" id="new_game">
</div>
</div>
View:
var ClockView = Backbone.View.extend({
el: '#view',
initialize: function () {
/** Define your variables in this View */
this.totalWait = 120;
this.secondsRemaining = this.totalWait;
this.hasFocus = true;
this.hasJustFailed = false;
},
events: {
'click #new_game' : 'startClock' /** The button starts the clock */
},
startClock: function () {
console.log("start clock");
var self = this; /** Save 'this' to a local variable */
setInterval(function () {
self.secondsRemaining -= 1;
self.displayTime();
if (self.secondsRemaining == 0) self.$('#timer').fadeOut(1000);
}, 1000);
},
displayTime: function () {
var minutes = Math.floor(this.secondsRemaining / 60);
var seconds = this.secondsRemaining - (minutes * 60);
if (seconds < 10) seconds = "0" + seconds;
var time = minutes + ":" + seconds;
this.$('#timer').html(time);
},
});
var clock_view = new ClockView();

Resources