React-countdown-now not updating on render - javascript

I am creating a Mission Clock web app using React and Flux.
The code can be found here: https://github.com/jcadam14/Flux-Mission-Clock
Right now it's extremely basic, I'm new to React and Flux and it has been an extremely long time since I did any JavaScript (been in the monolithic Java application business for too long). This is to get a proof of concept so I can base my design around React/Flux.
The basic concept is a "Next Contact" timer counts down and when it hits 1min before completion, the box the counter is in turns red. Then when the NextContact timer completes, a CurrentContact timer starts, and a new NextContact timer should start.
Everything works fine up until the point where the NextContact component completes and is supposed to update with a new NextContact. The text in the component and the style update, but the Countdown does not start ticking down. It stays at the new value but doesn't start the timer.
Each time a render occurs because of some other reason, the NextContact component updates again with a new time but does not start counting.
However, if I save any change within any of the files (I'm using Visual Studio Code with module.hot active) then the counter starts and in fact picks up where it should be. So it seems like something isn't getting fully rendered on change like I would expect.
I've tried using forceUpdate but that didn't do anything, and I've tried various ways of getting the Counter component but nothing works.
Any help would be greatly appreciated. I'm hoping once I get this down and can understand how all the dispatching stuff works the rest of the application should fall into place (the timers are a central component to the app, everything else is rather simple).
EDIT: I also tried writing just a simple timer app with Countdown but this time using Redux, and the same thing happens. So I guess the question might be how do you force a component to re-initialize itself?
Thanks!
Jason

Well I ended up just writing my own counter, here it is:
import React,{Component} from 'react';
const formatValues = ({days,hours,minutes,seconds}) =>
{
const hourString = ('0' + hours).slice(-2);
const minString = ('0'+ minutes).slice(-2);
const secString = ('0' + seconds).slice(-2);
return (days + ':' + hourString + ':' + minString + ':' + secString);
}
class MCCountdown extends Component
{
constructor(props)
{
super(props);
this.state = {
endDate:this.props.endDate,
countdown:'0:00:00:00',
secondRemaining:0,
id:0
}
this.initializeCountdown = this.initializeCountdown.bind(this);
this.tick = this.tick.bind(this);
}
componentDidUpdate(prevProps, prevState)
{
if(this.props.endDate !== prevProps.endDate)
{
clearInterval(prevState.id);
this.setState({endDate:this.props.endDate});
this.initializeCountdown();
}
}
componentDidMount()
{
this.initializeCountdown();
}
tick() {
const values = this.getTimeRemaining(this.state.endDate);
this.setState({countdown:formatValues(values),secondRemaining:values.secondsLeft});
if(values.secondsLeft <= 0)
{
clearInterval(this.state.id);
if(this.props.onComplete)
{
this.props.onComplete();
}
return;
}
else
{
if(this.props.onTick)
{
this.props.onTick(this.state.secondRemaining);
}
}
}
getTimeRemaining(endtime){
const total = Date.parse(endtime) - Date.parse(new Date());
const seconds = Math.floor( (total/1000) % 60 );
const minutes = Math.floor( (total/1000/60) % 60 );
const hours = Math.floor( (total/(1000*60*60)) % 24 );
const days = Math.floor( total/(1000*60*60*24) );
return {
secondsLeft: total,
days,
hours,
minutes,
seconds
};
}
initializeCountdown(){
const values = this.getTimeRemaining(this.state.endDate);
const id = setInterval(() => this.tick(),1000);
this.setState({id:id,countdown:formatValues(values),secondRemaining:values.secondsLeft});
}
render()
{
const {countdown} = this.state;
return(<div>{countdown}</div>);
}
}
export default MCCountdown
This did the trick. Seems like perhaps the timers/counters I have all tried might be missing that componentDidUpdate() method because once I added that to my MCCountdown, the clock restarts when a new date is set on the component.
Not sure if this is the pretties it can be but it works and I'm pretty darn happy with that right now.

Related

Creating Clock in React

I have a website and I want to put a realtime clock on one of the pages.
I have written the code using hooks but I understand using hooks for such cases is bad for performance due to rerenders in states every second.
Is there a less resource intensive solution?
import { useEffect, useState } from 'react'
export default function Footer() {
const [time, setTime] = useState()
useEffect(() => {
setInterval(() => {
const dateObject = new Date()
const hour = dateObject.getHours()
const minute = dateObject.getMinutes()
const second = dateObject.getSeconds()
const currentTime = hour + ' : ' + minute + ' : ' + second
setTime(currentTime)
}, 1000)
}, [])
return <div>{time}</div>
}
It's definitely good that you're thinking about performance however in this situation I wouldn't change a single thing. In order to change what gets rendered to the screen (in this case the text of the clock), the component has to be re-rendered to reflect the change in it's state, which is not such a bad thing. Each time the component re-renders it's not going to cause the parent to re-render so performance-wise we're doing just fine.

Update date and time when time is midnight

I am working on react dashboard which is to be displayed on large TV. It works fine for a single day. But when time reaches midnight dashboard gets stuck on same day and won't roll over to next day.
My approach
import React from 'react'
import moment from "moment";
function UpdateDate() {
const [{ startDate, endDate }] = useStateValue();
useEffect(() => {
var daterollOver = setInterval(function () {
var now = moment().format("HH:mm:ss");
if (now === "00:00:00") {
window.location.reload();
}
}, 1000);
return () => {
clearInterval(daterollOver);
}
}, [])
var url=`http:domain/live-dashboard?from=${startDate}&to=${endDate}`;
return (
<div>
<iframe
className="embed-responsive-item"
src={url}
></iframe>
</div>
)
}
export default UpdateDate;
I am using react context api to manage the state. Below is the section of the reducers.js which takes current time from moment.
import moment from "moment";
export const initialState = {
startDate: moment().startOf("day").local();,
endDate: moment().endOf("day").local();,
}
What would be the best option to refresh the page the at midnight which would update startDate & endDate and roll over to next day when time is midnight?
I'm naive when it comes to React myself, but from my understanding, you can use the useState hook to get/store things there.
I'd get the difference between now and midnight (represented here as endOf("day").add(1, 'ms')), then just set a timeout for that long. Don't bother checking every second or every minute or every hour. You know how much time needs to elapse, let that elapse. The setTimeout function is not terribly accurate, but on these scales it doesn't really matter. I wouldn't even check; just refresh. In the highly unlikely event it's too early, it will recalculate the next timeout to be very quick and resolve itself.
Using a state variable as the src of the iframe will cause a rerender of the HTML when the url changes, but that's fine -- you were reloading the page previously, this is less destructive than that.
I'm not sure if this works; I didn't bother creating a sandbox. Try it out, see if it helps. If it doesn't, please do create a sandbox (jsFiddle, codepen, whatever) to show it not working.
import React from 'react'
import moment from "moment";
function UpdateDate() {
function generateUrl() {
return `http:domain/live-dashboard?from=${moment().startOf("day")}&to=${moment().endOf("day")}`;
}
const [url, setUrl] = useState(generateUrl());
useEffect(() => {
let lastTimeout = 0;
let setupReload = function() {
const timeUntilMidnight = moment().diff(moment().endOf("day").add(1, "ms"), "ms");
return setTimeout(function() {
setUrl(generateUrl());
lastTimeout = setupReload();
}, timeUntilMidnight);
};
lastTimeout = setupReload();
return () => {
clearInterval(lastTimeout);
}
}, []);
return (
<div>
<iframe className="embed-responsive-item"
src={ url }>
</iframe>
</div>
);
}
export default UpdateDate;
I don't see you using startDate or endDate from context in your setInterval, so don't see the relevance of that? It's hard to say what's not working for sure but I would suggest a slightly different approach. Rather than checking every second if the time is exactly midnight... instead, I would check if the time is PAST midnight (using >=), and then also store the last time the page was refreshed, maybe in localstorage. Then you will change your logic to: is it after midnight, and more than 8 hours (or 23 hours... w/e) have elapsed since the last page refresh? Refresh the page. Something like that.

How to limit an API request to once per day in React?

I'm making a random color of the day web application using a random color API I found online. Everything is working so far, but since I'm new to JavaScript and React, I'm a bit curious on how I would limit an API request to once per day. The way the API works now is that every time you refresh the page, a new color will appear every time. Is there any way to limit this to one color that will appear per day - the same color - no matter how many times you refresh the page? Thanks!
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super()
this.state = {
items: [],
isLoaded: true
}
}
componentDidMount() {
fetch("http://www.colr.org/json/colors/random/7")
.then(res => res.json())
.then(res => {
this.setState({
isLoaded: true,
items: res.colors
});
})
}
render() {
var itemName = this.state.items.map(item => item.id)
var itemHex = this.state.items.map(item => item.hex)
//var itemHex = items.map(item => <div key={item.id}>{item.hex}</div>)
if (!(this.state.isLoaded)) {
return (
<div>
<h1>Not Loaded!</h1>
</div>
)
}
else {
return (
<section style={{ backgroundColor: "#" + itemHex[0]}} className="App">
<h1>JR's color of the day is: <h2 style={{color: "#" + itemHex[4]}}>{itemName[0]}.</h2></h1>
<h1>also, the hex is: {"#" + itemHex[0]}</h1>
<h4>here are some other colors that go w/ it</h4>
<div style={{backgroundColor: "#" + itemHex[1]}} className="rectangle1"></div>
<div style={{backgroundColor: "#" + itemHex[2]}} className="rectangle2"></div>
<div style={{backgroundColor: "#" + itemHex[3]}} className="rectangle3"></div>
<h3>data courtesy of the color API, colr.org</h3>
</section>
);
}
}
}
export default App;
you just need to store the date and colors on each fetch. and invalidate your cache based on today's date string and stored one.
componentDidMount() {
let cachedColors;
if(localStorage.getItem('cached-colors'))
cachedColors = JSON.parse(localStorage.getItem('cached-colors'));
// setting cachedColors to null if it wasn't stored today
if(cachedColors && new Date().toDateString() !== cachedColors.date)
cachedColors = null;
// if cachedColors still got value, it means we can use it as valid cache for today
if(cachedColors)
this.setState({
isLoaded: true,
items: cachedColors.value
});
else
fetch("http://www.colr.org/json/colors/random/7")
.then(res => res.json())
.then(res => {
this.setState({
isLoaded: true,
items: res.colors
});
})
}
How to limit an API request to once per day in React?
You can't, really. Rate-limiting an API is done on the server. Anybody can clear their cookies, or local storage, or whatever other means of persistence you use in the browser to rate-limit requests.
I realize this is a learning exercise, but there is no point in learning a technique that has no real-world use.
https://www.npmjs.com/package/memory-cache is the solution. Here are the examples of the API Usage.
This does what you're looking for by storing the current color in the browser for one day. The code should be clear, but just ask if it's not.
(Tested in Chrome with a Node backend, but should be fine in React)
let now = new Date().getTime(); // number of milliseconds since the 60's ended in London
const oneDay = 1000 * 60 * 60 * 24; // number of milliseconds in a day
if(localStorage.color && localStorage.expireTime && parseInt(localStorage.expireTime) > now){
let storedColor = localStorage.color; // or localStorage.getItem("color")
console.log(`Returning user -- Color from last visit is ${storedColor}`);
}
else{
let newColor = "green"; // Set your new color here
let newExpireTime = now + oneDay;
localStorage.setItem("color", newColor);
localStorage.setItem("expireTime", newExpireTime);
let dateString = new Date(newExpireTime).toLocaleString();
console.log(`First visit (since storage was cleared). New color, ${newColor}, will be replaced at ${dateString}`);
}
(Edit: Removed html output and added the info to console.log instead,
removed 'localStorage.clear()', which was for debugging)

how exactly use and update the global variables in react-native?

I am new to react-native. I wrote stop watch but the problem is that when I go to another page and then come back again the stop watch stop working. I want the stop watch keep working until the user press the stop button.I know, I should use global variable but I dont Know how to use it.here is my code:
export default class Record extends Component {
constructor(props) {
super(props);
this.state = {
stopwatchStart: false,
stopWatchTime: '00:00:00'
};
toggleStopwatch() {
if (!this.state.stopwatchStart) {
startDate = new Date();
startTime = startDate.getTime();
that = this;
setInterval(function () {
var date = new Date();
time = (date.getTime() - startTime) / 1000;
hour = parseInt(time / 3600);
timeAgo = parseInt(time % 3600);
min = parseInt(timeAgo / 60);
second = parseInt(timeAgo % 60);
that.setState({
stopWatchTime: (hour + ':' + min + ':' + second)
})
}, 1000);
}
this.setState({ stopwatchStart: !this.state.stopwatchStart });}
I think you should use redux for handling this...
https://redux.js.org/
I'm trying to understand what do you need, so I have the following question, do you want the interval to continue running after you change the screen?, because the components do not work that way.
You must follow the life cycle of the components (https://reactjs.org/docs/react-component.html), therefore, after you change the screen of the "Record" component, it will be unmount and the interval will destroy.
You may describe a little more what do you want to do with the interval, in case you want to use it in more than one component.
I still don't understand what you mean by view but probably you are opening another app or taking app to background. To persist data in your React Native application you should use AsyncStorage. In future you might want to use Sqlite, Redux persist or something else like these ones to keep your data persistent.
You can set this before app goes to background.
try {
await AsyncStorage.setItem('timeKey', myTimeVar.toString());
} catch (error) {
// Error saving data
}
And by using your key you can get the value of latest
try {
const value = await AsyncStorage.getItem('timeKey');
if (value !== null){
// We have data!!
console.log(value);
}
} catch (error) {
// Error retrieving data
}
You don't have to run timer in background by doing simple extraction (appClosedTime-currrentTime+timeWastedInFirstSession) you can achieve your goal.

Using react, it is possible to make an app that uses axios, setInterval, setTimeout, and map?

I have an issue which I'm beginning to suspect has no solution unless I drop React and return to jQuery. I want to create an app that is similar to https://tenno.tools/ or https://deathsnacks.com/wf/ These are sites which grab JSON data and update periodically.
I want to make a react app that uses axios to refresh the data once per minute with setTimeout, since the data changes often.
axiosFunc = () => {
axios.get('https://api.warframestat.us/pc').then(results => {
this.setState({
alerts: results.data.alerts
});
setTimeout(this.axiosFunc,1000 * 60);
})
}
componentDidMount() {
this.axiosFunc();
}
Next I need to use map to cycle through the alert array's objects and make individual components based off the objects' data that are active.
render() {
return (
<main className="content">
<header>{this.state.whichEvent.toUpperCase()}</header>
{this.state.alerts.map(alert => {
//Variables that pull time based data from the objects go here, and go into the component as props
<AlertsBox key={alert.id}/>
})}
</main>
);
}
Then I use the props and state within the component to make a timer, since the data from the JSON file have expiration dates...
let timer = () => {
//Extract the data from the original string
//Convert the UTC to locale time
let seconds = Math.round((this.state.eta/1000) % 60);
let minutes = Math.floor( (this.state.eta/1000/60) % 60 );
let hours = Math.floor( (this.state.eta/(1000*60*60)) % 24 );
let days = Math.floor( this.state.eta/(1000*60*60*24) );
return `${days >= 1? days + " days" : ""} ${hours >= 1? hours + " hrs" : ""} ${minutes} min ${seconds} sec`
}
And all of this works. I'm able to see the dynamic data from the JSON as they come in and leave, as well as the corresponding time. Now I just need to use setInterval in order to get the timer to tick every second. Is this possible? I asked a similar question here
How can I return values once per second using react and axios?
But again, I'm beginning to suspect that this isn't actually possible. Is it?
You'll want to use setInterval on the axiosFunc, no need to set that up inside the network request. Here's an example that calls your API every 5 seconds and renders a formatted date.
class Example extends React.Component {
constructor() {
super();
this.state = { alerts: [] };
}
axiosFunc = () => {
axios.get('https://api.warframestat.us/pc').then(results => {
this.setState({
alerts: results.data.alerts,
});
console.log('Updated the data!', results);
});
};
timer = time => {
// Your timer code goes here, just printing out example data here.
const date = new Date(time);
return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
};
componentDidMount() {
this.axiosFunc();
this.interval = setInterval(this.axiosFunc, 5000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
if (!this.state.alerts.length) {
return <div />;
}
// Sorting the alerts every time we render.
const latest = this.state.alerts.sort((a, b) => {
return new Date(b.activation) - new Date(a.activation);
})[0];
return <div>{this.timer(latest.activation)}</div>;
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.17.1/axios.min.js"></script>
<div id="root"></div>
It's definitely possible. As you said, all of this works - which part is actually giving you trouble? Are you getting an error anywhere?
Personally, I'd think about using Redux in addition to React in an app like this because I like to separate the fetching of data from the presentation of data, but that's all just personal preference. I have an example app that uses setInterval directly in a React component, in case the move from setTimeout to setInterval is causing you pain.

Categories