is it necessary to put this in useMemo ? (date calculations) - javascript

I use a timer and I want to ask you should I wrap this with useMemo and callback ?
without:
const [time, setTime] = useState();
const calculateInitialDuration = (endDate: string, today: Date): CountdownResult => {
const futureDate = new Date(endDate)
const days = differenceInDays(futureDate, today)
const hours = differenceInHours(futureDate, today) % 24
const minutes = differenceInMinutes(futureDate, today) % 60
const seconds = differenceInSeconds(futureDate, today) % 60
return { days, hours, minutes, seconds }
}
useEffect(() => {
const timer = setInterval(() =>
setTime(calculateInitialDuration(expire_date, new Date())), 1000);
return () => clearInterval(timer);
})
With memos
const calculateInitialDuration = useCallback(() => {
const days = calcDay;
const hours = calcHours;
const minutes = calcMinute;
const seconds = calcSeconds;
return { days, hours, minutes, seconds };
}, [time]);
const calcDay = useMemo(() => (differenceInDays(new Date(expire_date), new Date())), [time]);
const calcHours = useMemo(() => (differenceInHours(new Date(expire_date), new Date()) % 24), [time]);
const calcMinute = useMemo(() => (differenceInMinutes(new Date(expire_date), new Date()) % 60), [time]);
const calcSeconds = useMemo(() => (differenceInSeconds(new Date(expire_date), new Date()) % 60), [time]);
so is that necessary to wrap all calculations with memo or should I use the simple without useMemo ?
the second problem it does not lose 1 second, sometimes it looses 2 seconds

It's not a good idea to try to achieve an exact time different in the browser. setInterval/setTimeout can only guarantee you a minimal time when your function will be called.
As for memoization: the usual recommendation to use it - use it for heavy calculation OR if you pass some variable to another component as a prop.
For example:
const memoized = useMemo(() => {....}, [])
return <SomeComponent prop={memoized}/>
Unless you are in one of the above situations and have visible problems with performance, using unnecessary memoization may end up being worse for performance.

If you're trying to calculate a date difference, wouldn't you want it to be from the specific instance that the method was called?
I.E. Instead of using new Date() for every call, wouldn't it make more sense to have Date now = new Date(); and then pass in now for each call?
At the very least, that would remove the loss in seconds, would it not?
EDIT: Realizing this question is in relation to react, which I am unfortunately unfamiliar with. If this answer is somehow correct, then awesome. Otherwise, sorry for wasting your time.

Related

Finding objects that are within a time range of each other

I am using an external API to fetch a list of events happening between two dates. I have then used array.reduce to group the events happening on the same day into one array.
const time = events && events.reduce((acc, item) => {
if (!acc[item.fixture.date.split('T')[1]]) {
acc[item.fixture.date.split('T')[1]] = [];
}
acc[item.fixture.date.split('T')[1]].push(item);
return acc;
}, {})
They are labelled by the time in which the event occurs. If I console.log time then you can see in the image below how the data is returned for one day.
Example of returned data
I am trying to work out how to loop through these objects and find the ones that are within 30 minutes of each other. For example: It would find 16:05:00+01:00 and 16:30:00+01:00 and place these into a new array called Interval together.
What would be the easiest way to achieve this?
const datesInRange = (date1, date2, range) => //range = difference in minutes
(date1 - date2) / 1000 / 60 <= range
? true : false

Array inside useEffect to use in if Statement

I'm trying to use an if statement in my code where I want it to 'open' a Calendar Box if the date of today has occurred as well as for the past days of my calendar to open.
Here is my code where I'm using an useEffect to post it on loading the React Component:
// Call on post method via axios
useEffect(async () => {
console.log(daysData);
const daysDay = daysData.map((day) => day.day);
console.log(daysDay);
if (date + 1 >= daysDay) {
// Url where to post
await axios.post(`http://localhost:5001/open/chocolate`, {
day: date,
});
alert('New day is available to eat!');
}
setOpenCalendarBox('');
}, []);
I'm trying to get an array I've initiated a few lines above of the useEffect function (daysData) and I want the value of the 'day' item inside of the objects inside of the array and then compare the date of today if it is equal to or less than daysDay (day item inside of daysData)
Here is the code for my array:
// Here I initalize the array with useState
const [daysData, setDaysData] = useState([]);
// Here is the port I'm fetching my array from.
useEffect(() => {
fetch('http://localhost:5001/chocolates')
.then((resp) => resp.json())
.then((data) => setDaysData(data));
}, []);
And here is the date code:
// Initiate new Date
const current = new Date();
// Retrieve current day of the month
const date = current.getDate();
I can't seem to get the effect I want. I basically only want to see if the day has passed or if it is today then I want it to post to '/open/chocolate'.
That's probably because the value of daysData is set asynchronously, yet the useEffect block that depends on it does not list it as a dependency. Therefore you are invoking logic, which requires daysData to be populated asynchronously, when the component is loaded at runtime. So daysData will be empty.
A solution is to simply add daysData in the dependency array, so that you will only execute whatever logic that is in there once the array is successfully populated.
On the other hand, you are comparing a number against an array: which will give an unexpected result. If you want any of the day to meet date + 1, use daysDay.some(d => date + 1 >= d). If you want all of the days to meet date + 1, use daysDate.every(d => date + 1 >= d).
useEffect(async () => {
const daysDay = daysData.map((day) => day.day);
// This needs to be fixed, see comment for options
if (daysDay.some(d => date + 1 > d)) {
// Url where to post
await axios.post(`http://localhost:5001/open/chocolate`, {
day: date,
});
}
setOpenCalendarBox('');
}, [daysData]);

How to calculate difference between two moments in react native

I'm trying to calculate the duration (difference between two moments) for example currently I'm working on a react native project in which I have to calculate the time spent on a screen/card.
So right now I'm trying to calculate time spent like this:
import moment from 'moment';
// lets say i have 3 states to note time
const [startTime, setStartTime] = useState(moment());
const [endTime, setEndTime] = useState(moment());
const [elapsedTime, setElapsedTime] = useState(Number);
// assume i have a bottomSheet, so whenever that bottom sheet goes up the timer starts and when it goes down to original position, timer stops and calculate difference/duration in minutes.
// so for the onOpenStart on onCloseEnd props of the bottomsheet, this is my function:
const start = () => {
setStartTime(moment());
}
const end = () => {
setEndTime(moment());
const et = moment.duration(endTime.diff(startTime)).asSeconds();
setElapsedTime(et);
console.log(elapsedTime) // if i try for 10 seconds it logs 34, or sometimes -6, it's just not right
}
I tried to do with new Date method and if i try for 10 seconds it logs 34, or sometimes -6, it's just not right but could not succeed...
Any help would be appreciated.
In your particular case, I don't think you even need moment library. It's super heavy in the bundle and it's just not needed.
You don't need to initialise the state with the dates. You don't care about them when the component just mounts, do you? You want to keep track of the dates when your functions start and end get triggered.
The current behaviour is that way because useState is async. That means that you're trying to to set the state and then use this value in the next line for some calculation - that just won't work.
What you can do is to use useRef instead of useState. This way you will make sure that the value that you have is the freshest one across your component.
const startTime = useRef(new Date());
const endTime = useRef(new Date());
const [elapsedTime, setElapsedTime] = useState();
const start = () => {
startTime.current = new Date();
}
const end = () => {
endTime.current = new Date();
const et = (startTime.current - endTime.current) / 1000 // get the seconds
setElapsedTime(et);
console.log(et); // You cannot log elapsedTime here, because again, useState is async
}
I don't know what you need to do with the elapsed time, you might not need the state either. For now I've left it as state.
Last thing: I really recommend looking at day.js library, as it does the same things (in 99% of cases) as moment, but is much much lighter.
useState does not update synchronously. This means that although you setEndTime and then try to read it, the chances are it has not been updated yet.
Also, setting state causes a re-render. This is unnessasary when measuring performance. You can instead use useRef to hold the timing value in a reference variable.
export default function App() {
const [visible, setVisible] = React.useState(false);
const timer = React.useRef(null)
return (
<View style={styles.container}>
<Pressable
onPress={() => {
timer.current = new Date().getTime()
setVisible(true);
}}>
<Text>Show modal</Text>
</Pressable>
<Pressable
onPress={() => {
setVisible(false);
alert(new Date().getTime() - timer.current)
}}>
<Text>Hide modal</Text>
</Pressable>
{visible ? (
<Card>
<Text>Modal content</Text>
</Card>
) : null}
</View>
);
}
Here, we are setting the visible value and storing timestamp on press. And when the user presses hide, we use the stored time to calculate the time elapsed.

How to filter last day in an array?

I have an array of objects like this:
[
{
created: "2019-08-14T13:24:36Z",
email: "test1#gmail.com"
},
{
created: "2019-08-15T13:24:36Z",
email: "test2#gmail.com"
},
{
created: "2019-08-16T13:24:36Z",
email: "test1#gmail.com"
},
{
created: "2019-08-22T13:24:36Z",
email: "test4#gmail.com"
},
{
created: "2019-08-22T15:29:66Z",
email: "test1#gmail.com"
}
]
The array is sorted by created. I want to filter those records which are on the last day, irrespective of the time on that day. I added the timestamp using moment.js. Something on these lines:
router.get('/GetLastDayRecords', (req, res) => {
res.json(allRecords.filter(record => record.created.max()));
});
Split the task: first get the maximum date which you'll find at the end of the sorted array (just getting the "YYYY-MM-DD" part of it is enough) and then launch the filter:
let max = allRecords.length ? allRecords[allRecords.length-1].created.slice(0,10) : "";
res.json(allRecords.filter(({created}) => created >= max));
First you need to figure out which day is the last day. If you can assume the records are already sorted, then this is pretty simple:
// Assuming your records are stored in the variable "records"
var lastDay = records[records.length - 1].created;
Now here's where your specific answer may differ based on how you want to handle time zones. Suppose one event happened at 11 PM EST (3 AM GMT) and another event happened at 1 AM EST (5 AM GMT). Are these the same day? In Europe they are, but in America they aren't!
What you need to do is create some cipher from the date+time listed to a "day". This way you can compare two "days" to see if they're the same:
lastDay = new Date(lastDay);
// Setting hours, minutes, and seconds to 0 will give you just the "day" without the time, but by default will use the system timezone
lastDay.setHours(0);
lastDay.setMinutes(0);
lastDay.setSeconds(0);
Once you know which day was the last, it's a simple filter:
// Using a for loop
var results = []
for (var i = 0; i < records.length; i++)
{
if (records[i].created > lastDay) {
results.push(records[i]);
}
}
// Using .filter
var results = records.filter(x => x.created > lastDay);
Alternatively, since we know it's already sorted, we can do it a bit more efficiently by binary searching for the first record on the last day, then grabbing all records after that:
var test = records.length / 2;
var step = records.length / 4;
var found = false;
while (!found) {
if (records[test].created < lastDay) {
test += step;
step /= 2;
}
else if (records[test].created > lastDay) {
if (step == 1) {
// We found the exact cut-off
found = true;
}
else {
test -= step;
step /= 2;
}
}
}
var results = records.slice(test);
Because you're only interested in the "last" day, the logic is a bit simpler. If you wanted the "third" day, you would need to check if created was after the start of the third day and before the end of the third day. We can just check if it's after the start of the last day.
I would create a function to turn your created properties into data be easily compared.
I would also avoid trying to do the entire filter operation in one or two lines as it will difficult to read by other developers.
const dateToInt = date => parseInt( date.split('T').shift().replace(/-/g, '') );
The above will:
Split your created property into an array of date and time.
Select the first element, which happens to be the date.
Remove the dashes in the date.
Coerce the value into a number.
With this you can find the maximum value and filter based on that value.
const nums = foo.map( ({ created }) => dateToInt(created) )
First get a list of numbers from the dataset.
const max = Math.max( ...nums )
Get the biggest number in the list.
const lastDays = foo.filter( ({ created }) => dateToInt(created) === max )
With all that setup, getting the max date is very easy and readable.
Of course, since the list is already sorted. You could have just done this as well.
const last = foo[foo.length -1].created;
const lastDays = foo.filter( ({ created }) => created === last )
I wrote a solution using reduce and filter:
const lastDay = arr.reduce((acc, el) => {
const date = el.created.substr(0,10);
const oldDate = new Date(acc);
const nextDate = new Date(date);
if(oldDate.getTime() > nextDate.getTime()) {
return oldDate;
} else {
return nextDate;
}
}, '1900-01-01');
const lastDayArr = arr.filter(el => {
const date = el.created.substr(0,10);
const oldDate = new Date(lastDay);
const nextDate = new Date(date);
return (oldDate.getTime() === nextDate.getTime());
});
First, you find the most recent date, reducing the original array by comparing which date is the most recent, for this you drop the part of the created string that specifies the hours/minutes/seconds.
You can use a very distant in time date as initial value, or you can set it to null and add another validation in your callback function.
As a second step, you use filter, using the same technique of dropping the hours/minutes/seconds of the created string.
The end result is an array of the elements with the most recent date in your original array.
If you can assume the array is sorted, you can skip the reduce method and just do:
const lastDay = arr[arr.length - 1].created.substr(0,10);
This should work:
allRecords.filter( record => {
let last_date = allRecords[ allRecords.length - 1].created
return last_date.slice(0, 10) === record.created.slice(0, 10)
})
Basically, you are getting the last element from your array and slicing its created value down to its date. Then you are slicing your current record's created value down to its date and comparing if they are the same.
Assuming that the array is already ASC ordered:
const onLastDay = values.filter( v => {
const last = moment(values[ values.length - 1 ].created)
const differenceInDays = last.diff(moment(v.created), 'days')
return differenceInDays < 1
})
console.log(onLastDay)
NOTE: If you try with the reported array you get an error due the fact that the last date is not valid! There are 66 seconds!

RxJS: Debounce with regular sampling

I have an Observable that emits a stream of values from user input (offset values of a slider).
I want to debounce that stream, so while the user is busy sliding, I only emit a value if nothing has come through for, say 100ms, to avoid being flooded with values. But then I also want to emit a value every 1 second if it is just endlessly debouncing (user is sliding back and forth continuously). Once the user stops sliding though, I just want the final value from the debounced stream.
So I want to combine the debounce with a regular "sampling" of the stream. Right now my setup is something like this:
const debounce$ = slider$.debounceTime(100),
sampler$ = slider$.auditTime(1000);
debounce$
.merge(sampler$)
.subscribe((value) => console.log(value));
Assuming the user moves the slider for 2.4 seconds, this emits values as follows:
start end
(x)---------|---------|---(x)|----|
| | | |
1.0 2.0 2.5 3.0 <-- unwanted value at the end
^ ^ ^
sample sample debounce <-- these are all good
I don't want that extra value emitted at 3 seconds (from the sampler$ stream).
Obviously merge is the wrong way to combine these two streams, but I can't figure out what combination of switch, race, window or whatever to use here.
You can solve the problem by composing an observable that serves as a signal, indicating whether or not the user is currently sliding. This should do it:
const sliding$ = slider$.mapTo(true).merge(debounce$.mapTo(false));
And you can use that to control whether or not the sampler$ emits a value.
A working example:
const since = Date.now();
const slider$ = new Rx.Subject();
const debounce$ = slider$.debounceTime(100);
const sliding$ = slider$.mapTo(true).merge(debounce$.mapTo(false));
const sampler$ = slider$
.auditTime(1000)
.withLatestFrom(sliding$)
.filter(([value, sliding]) => sliding)
.map(([value]) => value);
debounce$
.merge(sampler$)
.subscribe(value => console.log(`${time()}: ${value}`));
// Simulate sliding:
let value = 0;
for (let i = 0; i <= 2400; i += 10) {
value += Math.random() > 0.5 ? 1 : -1;
slide(value, i);
}
function slide(value, at) {
setTimeout(() => slider$.next(value), at);
}
function time() {
return `T+${((Date.now() - since) / 1000).toFixed(3)}`;
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>
For those who are interested, this is the approach I took, inspired by #cartant's answer.
const slider$ = new Rx.Subject();
const nothing$ = Rx.Observable.never();
const debounce$ = slider$.debounceTime(100);
const sliding$ = slider$.mapTo(true)
.merge(debounce$.mapTo(false))
.distinctUntilChanged();
const sampler$ = sliding$
.switchMap((active) => active ? slider$.auditTime(1000) : nothing$);
debounce$
.merge(sampler$)
.subscribe(value => console.log(`${time()}: ${value}`));
The difference is adding distinctUntilChanged on the sliding$ stream to only get the on/off changes, and then doing a switchMap on that to either have the sampler return values or not.

Categories

Resources