Calculating time difference for sets of streaming data - javascript

I have the following function that renders a row based on streaming SignalR data coming from another component:
let prev
let next
renderRow = () = {
this.props.incomingData.map(item => {
return (
<tr>
<td>{item.time}</td> //00:00:00 format
<td>{this.calculateSecondsTaken()}</td> //should show the time difference in seconds
<td>{item.event}</td>
<td>{item.message} <br/>{item.outputURL}</td>
</tr>
)
})
}
I'm trying to get the time difference for each data row based on the item.time attribute value by subtracting current time from previous time (in the row).
//calculates the difference between the previous and the incoming (next) time value to give the time taken
//for the current row's data to come in.
calculateSecondsTaken = (prev, next) => {
next - prev;
}
I know in the above function it has to be some sort of a subtraction based on previous and next values of time but I can't quite pin down the logic of it. For instance, what would the difference be for the very first row etc.
Edit:
The data streaming component is as follows
deploymentEventMessageReceived = (item: DeploymentEvent) => {
let dataset = {
time: item.UtcNowPrettyText, //this is the time value
event: item.EventName,
message: item.Message,
outputURL: item.OutputUrl
}
let newDataArray = this.state.receivedData.concat(dataset);
this.setState({
receivedData: newDataArray
});
}
deploymentEventMessageReceived() in turn gets called inside the function where I'm connecting to SignalR.
Edit 2 updated (working version):
deploymentEventMessageReceived = (item: DeploymentEvent) => {
let lastEntryTime;
newArray = newArray.concat(item);
if (newArray.length == 1) {
lastEntryTime = moment(newArray[newArray.length - 1].UtcNowPrettyText)
} else {
lastEntryTime = moment(newArray[newArray.length - 2].UtcNowPrettyText)
}
timeDiff = moment(item.UtcNowPrettyText).diff(lastEntryTime, "seconds");
//create new object with the addition of TimeDifference attribute
let dataset = {
UtcNowPrettyText: item.UtcNowPrettyText,
EventName: item.EventName,
Message: item.Message,
OutputUrl: item.OutputUrl,
TimeDifference: timeDiff
}
//store it in a new array
finalDatasetArray = finalDatasetArray.concat(dataset);
this.setState({
receivedData: finalDatasetArray
});
}
Inside render() of data streaming component
<RenderRow receivedData={this.state.receivedData} />
Any help is appreciated.

I would implement a time difference calculation this way: I keep track of Date.now(), which I don't render but keep it in each entry so I can perform math on it. I don't know how you're planning on rendering the time difference, but I put it in this.state in my example.
deploymentEventMessageReceived = (item) => {
let dataset = {
time: item.UtcNowPrettyText, //this is the time value
event: item.EventName,
message: item.Message,
outputURL: item.OutputUrl,
date: Date.now()
}
let lastEntry = this.state.receivedData[this.state.receivedData.length - 1]
let timeDifference = dataset.date - lastEntry.date //time in ms
let newDataArray = this.state.receivedData.concat(dataset);
this.setState({
receivedData: newDataArray,
timeDifference: timeDifference
});
}
edit: My personal implementation would be to actually split up the two concepts. I keep Date.now() and make a different function to actually build the array of time differences. Never know if you will need Date.now() later on, and it just makes more sense to have a timestamp in its entirety in your data. You don't need to add the item immediately. You can concat it later. I use object destructuring to make the assignment cleaning as 2 one-liners.
deploymentEventMessageReceived = (item) => {
let { UtcNowPrettyText, EventName, Message, OutputUrl } = item
let dataset = { UtcNowPrettyText, EventName, Message, OutputUrl, Date: Date.now() }
this.setState({receivedData: this.state.receivedData.concat(dataset)})
}
buildTimeDifferences = (entries) => {
let newArr = []
for (let i = 0; i < entries.length; i++) {
if (i !== entries.length - 1) {
newArr.push(entries[i + 1].Date - entries[i].Date)
}
}
return newArr
}
d = [{Date: 1},{ Date: 2}, {Date: 4}, {Date: 7}]
console.log(buildTimeDifferences(d))

Related

How can I update state when data from one state is changed and put into new state

So I have a parent component (Weather.jsx) that gets forcast weather from an api and passes it to Forcast.jsx this component just passes the whole data array to Tomorrow.jsx. When the inital data changes it is not updating the lower components, I have an assumption that it is because inside Tomorrow.jsx new state is set and then updated so perhaps it does not change there when new data is passed.
Tomorrow.jsx
This code below is from Tomorrow.jsx and shows how I get the data and update an empty array which is then passed into new state and then mapped over and passed to another component which displays the data.
const [tomorrowForcast, setTomorrowForcast] = useState([]) // Create empty state to hold tomorrowForcast once extracted
useEffect(function () {
const forcastArray = props.tomorrowForcast.list // Get full array from props - Weather.jsx > Forcast.jsx > Tomorrow.jsx
const date = new Date(); // Get full date
const date1 = date.getDate(); // Get Day
const date2 = date1 + 1 // Add one onto todays day
const newTomorrowForecasts = []; // Create empty array to hold tomorrows forcast pulled from array below
forcastArray.forEach(forcast => {
let get_current_dt = forcast.dt_txt // Get date and time
let split_dt = get_current_dt.split(" ") // Split date and time
let get_full_date = split_dt[0] // Get just date
let get_date = get_full_date.slice(8) // Cut date down to just day
// Check if tomorrows day is same as array item
if( get_date == date2){
newTomorrowForecasts.push(forcast); // If so update empty array
}
})
setTomorrowForcast(newTomorrowForecasts) // Push array to empty state
}, []);
// This gets the state maps over it and passes it to child component to display
const forcasts = tomorrowForcast.map(forcast => {
let tomorrowObj = {...forcast, identifier: 'tomorrow'}
// Get time
const get_dt = forcast.dt_txt
const split_dt = get_dt.split(" ")
const get_time = split_dt[1]
const time = get_time.slice(0, 5)
return (
<WeatherBlock {...tomorrowObj} d_or_t={time} />
)
})
The use effects dependency array is empty as a result the function get's triggered only once.
You have to add props.tomorrowForcast into the useEffects dependency.
useEffect(function () {
}, [props.tomorrowForcast]);

Stop rendering UseEffect of Click of a specific function

In my Application when ever i change the number i want run the UseEffect Hook and re-render the updated values in the array which is working, but one condition i don't want to run the use UseEffect Hook is when i click addRow function i want bank field should be added in the rows except the number
JsCode
const [number, setNumber] = useState(3);
const [data, setData] = useState([]);
const [insValue,setInstValue]=useState(90);
useEffect(() => {
const list = [];
for (let i = 1; i <= number; i++) {
let ins = instValue/number;
list.push({
insNo: i,
installmentAmount: ins,
chequeNumber: '',
});
}
setData(list);
}, [number]);
const addRow = () => {
setNumber((number) => number + 1);
console.log('Add item clicked');
data.push({
insNo: number + 1,
installmentAmount: '',
chequeNumber: ''
});
const list = [...data];
setData(list);
};
Html
<div onClick={addRow}>
ADD ROW
</div>
currently what it is happening is when i click on add row number get changed and useffect Runs and data get updated i need is blank field in the row on the last object of an array
Output Getting
[{"insNo":1,"installmentAmount":22.5,"chequeNumber":""},
{"insNo":2,"installmentAmount":22.5,"chequeNumber":""}
{"insNo":3,"installmentAmount":22.5,"chequeNumber":""}
{"insNo":4,"installmentAmount":22.5,"chequeNumber":""}]
Output i need on click of addRow
[{"insNo":1,"installmentAmount":30,"chequeNumber":""},
{"insNo":2,"installmentAmount":30,"chequeNumber":""}
{"insNo":3,"installmentAmount":30,"chequeNumber":""}
{"insNo":4,"installmentAmount":"","chequeNumber":""}]
It's hard to understand what you exactly want.
But according to your code, you can't get the desired result.
Because you increase number and add a new row with empty values. And useEffect updates your array data again. So the installmentAmount in the last row will be filled.
To avoid it, you can change the code block like the following:
const initialNumber = 3; // Define initial value to check if data array is in the initial step.
const [number, setNumber] = useState(initialNumber);
// ... ...
useEffect(() => {
const list = [];
for (let i = 1; i <= number; i++) {
list.push({
insNo: i,
installmentAmount: i > initialNumber && i == number? '' : instValue/number,
chequeNumber: '',
});
}
setData(list);
}, [number]);
const addRow = () => {
setNumber((number) => number + 1);
console.log('Add item clicked');
const list = [...data];
list.push({
insNo: number + 1,
installmentAmount: '',
chequeNumber: ''
});
setData(list);
};
The data is refreshed again into the useEffect when you change number, losing all data.
You can avoid change number state at click, since the new data is already being saved manually.
setNumber((number) => number + 1);

React.js and Firebase querySnapshot.forEach replacing array with identical objects

I've been facing this issue for a couple of days where I have an array named tempTaskArray, which stores objects filled with data taken from my firestore database.
These objects contain the correct data on each iteration of the .forEach method but when put into my state variable, "tasks" (an array), from array tempTaskArray, all objects contain the data of the last created object.
This issue is not run into when I have an array of strings.
See code below:
const getTasks = () => {
db.collection("users").doc(uid).collection("tasks")
.orderBy("epochdate", "asc")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// populates array with strings
// tempTaskArray.push(doc.data().taskname)
// tempTaskObject is an empty object
tempTaskObject.taskname= doc.data().taskname
tempTaskObject.duedate= doc.data().duedate
// gets epoch date value and stores it
var epochDate = doc.data().epochdate;
// gets current time
const day = new Date();
let currentTime = day.getTime();
// finds time remaining in epoch
var timeRemaining = epochDate - currentTime;
// finds how many days are left and rounds down
var daysLeft = (timeRemaining / 86400000)
daysLeft = Math.floor(daysLeft)
tempTaskObject.duein = daysLeft
tempTaskArray.push(tempTaskObject)
});
setTasks(tempTaskArray)
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
}
getTasks();
The data taken populating state variable "task" is then rendered in component Task seen below:
<div>
{tasks.map((task) => (
<Task
key={task}
task__title={task.taskname}
due__date={task.duedate}
days__away={task.duein}
/>
))}
</div>
I would be extremely grateful for an explanation of why this is happening. Thanks in advance
Based on the provided code I believe it's an error of adding the reference of the tempTaskObject to the tempTaskArray and changing the same referenced object on each iteration.
To avoid this you can reassign a new Object to tempTaskObject on every iteration, like
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// tempTaskObject is (now really) an empty object
tempTaskObject = {};
tempTaskObject.taskname= doc.data().taskname;
Further Explanation
I believe it worked before with String types, because these are primitive datatypes which will just be copied into the tempTaskArray.
The current tempTaskObject though has multiple properties (duein, taskname, duedate) and is therefore a complex object. When pushing objects to an javascript array, it adds the reference to the object to the array, NOT a copy. In your example you always keep the reference to the tempTaskObject and that's why it always changes the properties of the exact same object.
Examples
// Array with Primitives
const animalNames = ['Tiger', 'Spider'];
console.log('animalNames', animalNames);
let scaryName = animalNames[1];
scaryName = 'Goldfish';
console.log('still scary animalNames', JSON.stringify(animalNames)); // nothing changed, because we're working with a copy
// Array with Objects
const animals = [{
name: 'Tiger',
teeth: 30,
legs: 4
}, {
name: 'Spider',
teeth: 0,
legs: 8
}];
console.log('animals', animals);
let scaryAnimal = animals[1]; // storing a reference to the spider object
scaryAnimal.name = 'Goldfish';
scaryAnimal.legs = 0;
console.log('less scary animals', animals); // spider got replaced by a goldfish
// Now we'll want to add another animal the WRONG WAY
// FIX: Uncomment line below to properly add a bunny
// scaryAnimal = {};
scaryAnimal.name = 'Bunny';
scaryAnimal.legs = 4;
scaryAnimal.teeth = 2;
animals.push(scaryAnimal);
console.log('duplicated animal', JSON.stringify(animals));
Edit: Fixed typo

What is the simplest way of giving Tone.js arrays of notes and durations in seconds to play back?

I would like to give Tone.js a list of notes and corresponding durations for each note and have it play back the sequence. As far as I can see, there is no easy way to do this.
In the following, the corresponding time values are not the ones I entered (i.e 0.25, 0.5, 0.25), as evidenced by the console.log:
var part = new Tone.Part(function(time, note){
console.log(time);
console.log(note);
synth.triggerAttackRelease(note, time);
}, [[0.25, "C2"], [0.5, "C3"], [0.25, "G2"]]);
part.start(0).loop = false;
Tone.Transport.start();
How can I give Tone.js notes and corresponding ms for playback?
I'm not familiar with Tone.js, so there's probably a better way of doing this. The official example for the array shorthand that you're using doesn't seem to work, so it might be a library issue.
As for what you're trying to achieve, I fiddled with it out of curiosity and here's what I've come to:
function timeFromDurations(value, i, arr) {
const prevTime = arr[i - 1]?.time;
value.time = prevTime + arr[i - 1]?.duration || 0;
return value;
}
const notesAndDurations = [
{ note: 'C3', duration: .25 },
{ note: 'C4', duration: .5 },
{ note: 'G2', duration: 1 },
].map(timeFromDurations);
console.log(notesAndDurations);
const synth = new Tone.Synth().toDestination();
// use an array of objects as long as the object has a "time" attribute
const part = new Tone.Part((time, value) => {
// the value is an object which contains both the note and the velocity
synth.triggerAttackRelease(value.note, value.duration, time);
}, notesAndDurations).start(0);
Tone.Transport.start();
The idea is that you need to set start time of each note based on previous note start time + duration. That removes the need to set the start time(not optional) manually.
Edit
For your second case where the durations and the notes come in separate arrays you can use the following reduce function:
const notes = ['C3', 'C4', 'G2'];
const durations = [0.25, 0.5, 1];
const noteDurationTime = notes.reduce((acc, note, i) => {
const prevTime = acc[i - 1]?.time;
const time = prevTime + acc[i - 1]?.duration || 0;
const duration = durations[i];
acc.push({ note, duration, time });
return acc;
}, []);
The idea is the same, you're building an array of objects that have all the needed properties(note, duration, time), but this time from different sources(notes array and durations array).
You want to make sure that both these arrays are the same length.

JavaScript Generator: implement a ticket queue system

I am trying to implement a ticket queue system, where by default it would have 3 different queues to holder tickets that are of severity 1, severity 2, and severity 3 respectively. And I have a method getTicketAtHighestSeverity in it that returns the oldest ticket in the highest severity queue, so it starts at the first queue and looks for the first item in the queue and moves onto the next queue if the current queue is empty and another method getTicketBySeverity to iterate through all the all queues return the ticket starting at the highest severity
Here is my implementation.
class ticketQueues {
constructor(numOfQueues = 3) {
this.queues = Array.from({length: numOfQueues}).fill([])
}
addSev1(ticket) {
this.queues[0].push(ticket)
}
addSev2(ticket) {
this.queues[1].push(ticket)
}
addSev3(ticket) {
this.queues[2].push(ticket)
}
*getTicketBySeverity() {
for(const queue of this.queues) {
for(const ticket of queue) {
yield ticket
}
}
return null
}
getTicketAtHighestSeverity() {
for(const queue of this.queues) {
for(const ticket of queue) {
return ticket
}
}
return null
}
}
However it seems like getTicketBySeverity is not working properly.
const queues = new ticketQueues()
queues.addSev1({timestamp: Date(), name: 't1'})
queues.addSev2({timestamp: Date(), name: 't2'})
queues.addSev3({timestamp: Date(), name: 't3'})
for(let i = 2; i >= 0; i--) {
console.log(queues.getTicketBySeverity().next().value) // 🚨 this keeps returning the first item from the queue
}
Because it is not moving to the next ticket as it only returns the first ticket. The reason I chose Generator to implement this method is that I wanted to take advantage of the lazy evaluation model because the data set can be huge, I don't want to necessarily have to get all of the tickets all at once.
Can someone fix my implementation with getTicketBySeverity. And any suggestions about the naming here? I feel like the naming here i.e. getTicketBySeverity and getTicketAtHighestSeverity might not be the best choice. Also, feel free to comment on my usage of Generator here if you think this might not be a legit use case for that.
One problem is
this.queues = Array.from({length: numOfQueues}).fill([])
.fill does not work well with non-primitives (usually), since each item in the new array will be a reference to the same object. You've only created a single array. The problem is the same as why the following doesn't work as one might expect:
const subarr = [];
arr.push(subarr);
arr.push(subarr);
since there's only one subarr.
Use a mapper function with Array.from to explicitly create a new array for each iteration:
this.queues = Array.from({length: numOfQueues}, () => []);
Also, to iterate over the iterator, use for..of - either that, or remove the found item from the array when it's found (otherwise, every time it's called, it'll return the same item).
You can control the number of tickets to remove at once with for..of by passing an argument to the generator and keeping track of the number of elements yielded:
class ticketQueues {
constructor(numOfQueues = 3) {
this.queues = Array.from({length: numOfQueues}, () => []);
}
addSev1(ticket) {
this.queues[0].push(ticket)
}
addSev2(ticket) {
this.queues[1].push(ticket)
}
addSev3(ticket) {
this.queues[2].push(ticket)
}
*getTicketsBySeverity(limit) {
let count = 0;
for(const queue of this.queues) {
while (queue.length) {
yield queue.shift();
count++;
if (count === limit) {
return null;
}
}
}
return null
}
}
const queues = new ticketQueues()
queues.addSev1({timestamp: Date(), name: 't1'})
queues.addSev1({timestamp: Date(), name: 't1-2'})
queues.addSev2({timestamp: Date(), name: 't2'})
queues.addSev3({timestamp: Date(), name: 't3'})
for (const ticket of queues.getTicketsBySeverity(3)) {
console.log(ticket);
}
console.log(queues.queues);

Categories