How to rename Object Keys & change Object values from string to number - javascript

Alright, guys.
I am dealing with <TimePicker/> third party library from React and want to manipulate the state so I can push this new format of code into a new object later.
when I target a value using this library, whatever time I pick, my state turns out to be a string like this - e.g if I pick 20:30, this.state.time equals "20:30".
I want to manipulate this string into an object like this:
from "20:30" to time: {hour: 20, minutes: 30} ///both hour and minutes are now numbers
My full code below:
import React from "react";
import TimePicker from "react-time-picker";
import './Clock.css'
class Clock extends React.Component {
state = {
time: '0:00',
pushTime: [],
value: ''
};
onChangeTimePickerHandler = time => {
this.setState({time});
let currentTime = this.state.time;
this.props.CallbackRenderTime(currentTime)
};
//I am using TimePicker library for React and want to manipulate the income from the chosen time
pushNewTimeHandler = () => {
let time = [...this.state.time] ;// ["1", "0", ":", "0", "0"]
time.splice(2, 1);
let timeToOneString = time.join().replace(/,/g, "");
let prepareStringForNewObject = (value, index) => {
let array = value.substring(0,index) + ',' + value.substring(index);
return array.split(',')
};
let newObj = {...prepareStringForNewObject(timeToOneString,2)};
console.log(newObj); // {0: "10", 1: "00"}
};
render() {
return (
<div>
<TimePicker
onChange={this.onChangeTimePickerHandler}
value={this.state.time}/>
<button onClick={this.pushNewTimeHandler}>Push the Array of Time</button>
<p>{this.state.time}</p>
</div>
)
}
}
export default Clock
So I have been playing around and came with this really ugly part-of-the-issue solution:
//I am using TimePicker library for React and want to manipulate the income from the chosen time
manipulateTimeHandler = () => {
//this.state.time is "10:00"
let time = [...this.state.time] ;// now it looks like this: ["1", "0", ":", "0", "0"]
time.splice(2, 1);
let timeToOneString = time.join().replace(/,/g, "");
let prepareStringForNewObject = (value, index) => {
let array = value.substring(0,index) + ',' + value.substring(index);
return array.split(',')
};
let newObj = {...prepareStringForNewObject(timeToOneString,2)};
console.log(newObj); //finally it became the object, but the object values are still
//strings and the object keys still need to be changed into "hour"
//and "minutes" {0: "10", 1: "00"}
};
So on my ugly solution pushNewTimeHandler I still need to rename the object keys to hour and minutes and also need to transform the string values for hours and minutes into numbers.

I'd split the time string by :, creating an array of strings, then map each string to a number, and destructure into hour and minutes on the left of the =. Then you can make an object of hour and minutes:
const str = '20:30';
const [hour, minutes] = str.split(':').map(Number);
const obj = { hour, minutes };
console.log(obj);

Related

How to dynamically change the date for the date key in the items prop for react-native-calendars

I am trying to change the date in the items prop for react native calendars.
From the docs, it shows that it requires a date string as a key which then requires an array of objects to display for that particular date.
Something like this.
<Agenda
items: {{
'2019-10-12': [],
'2019-10-13': [{}, {}],
'2019-10-14': [{}],
'2019-10-15': []
}}
/>
However, I do not want to hardcode the dates. What I would like is to display the dates of the next week. This is the approach I have tried
I get the date using
new currentDate = new Date();
I pass this date into weekly items to get my weekly items object.
The below function uses two other functions called addDays and dateToString to properly render the dates.
const weeklyItems = (date) => {
const day1 = dateToString(addDays(date, 1));
const day2 = dateToString(addDays(date, 2));
const day3 = dateToString(addDays(date, 3));
const day4 = dateToString(addDays(date, 4));
const day5 = dateToString(addDays(date, 5));
const day6 = dateToString(addDays(date, 6));
const currentDate = dateToString(date);
return {
currentDate : [],
day1: [],
day2: [],
day3: [],
day4: [],
day5: [],
day6: []
}
}
addDays and dateToString are my helper functions:
function addDays(date, days) {
var result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
This function converts a date object into the form 'YYYY-MM-DD' which is what is required by Agenda
function dateToString (date) {
const newString = moment(date).format('YYYY-MM-DD');
return newString.toString()
}
I have checked to see if my dates were correct and they all check out. I also have used currentDate and day6 as my objects for the minDate and maxDate props for Agenda and they work as expected.
After doing this my Agenda now looks like this:
<Agenda
minDate = {currentDate}
maxDate = {day6}
selected = {currentDate}
items: {weeklyItems(currentDate)}
renderEmptyDate={() => {return (<View/>);}}
/>
And this makes it stop working. I know that I have empty arrays in my weeklyItems but for now I am just trying to get the dates working and unfortunately they are not. Any help would be appreciated.
I`m on mobile, so i can't run this code...
Try to return your dates object like that:
return {
[currentDate] : [],
[day1]: [],
...
}
Also you can rewrite your weeklyItems functions to iterate from 0 to 6 and dynamically set properties to object which will be returned.
Than in your component:
// somewhere in your js
// const dates = weeklyItems(currentDate)
// const minDate = dateToString(date)
// const maxDate = dateToString(addDays(date, 6))
<Agenda
minDate = {minDate}
maxDate = {maxDate}
selected = {minDate}
items: {dates}
renderEmptyDate={() => {return (<View/>);}}
/>

What would be the proper way to filter by date

I have an array of objects with date strings formatted as so 2019-03-22T13:36:18.333Z
I am curious what the best course of action would be to sort these by past day, past week, past month and past year.
I was thinking of just splitting at the T then splitting again at the -
I was also considering putting the date into the Date object and figuring it out that way.
What would be the most dynamic? Most efficient? etc.
const dates = [
{created_at: '2019-03-22T13:36:18.333Z'}
{created_at: '2019-05-22T13:36:18.333Z'}
{created_at: '2019-03-23T13:36:18.333Z'}
{created_at: '2019-03-24T13:36:18.333Z'}
{created_at: '2019-01-22T13:36:18.333Z'}
]
const spliteDate = (date, splitBy) => {
if(splitBy = 'day'){
return date.split("T")[0].split("-")[1]
} else if(splitBy = 'month') {
return date.split("T")[0].split("-")[2]
} else if(splitBy = 'year') {
return date.split("T")[0].split("-")[0]
}
}
dates.filter(date => {
return splitDate(date, 'month') === Date.now().getMonth()
}
Something along these lines
It is probably the easiest to convert the strings to Date objects
For instance, you have an array of dates in string format:
const arr = ['2019-03-22T13:36:18.333Z', '2019-03-28T16:36:18.333Z', '2015-05-21T16:36:18.333Z'];
Looking at your question, if you would like to filter it by a certain date, it is definitely much shorter and efficient to do this:
const filterByDate = dateFilter => arr.filter(date => new Date(date).getDate() < dateFilter);
filterByDate(25);
// result [ '2019-03-22T13:36:18.333Z', '2019-05-21T16:36:18.333Z' ]
And, if you require multiple filters, for instance filter by date AND year,
const filterByDateAndYear = (dateFilter, yearFilter) => arr.filter(date => (new Date(date).getDate() < dateFilter) && (new Date(date).getFullYear() < yearFilter));
filterByDateAndYear(25, 2018);
//result [ '2015-05-21T16:36:18.333Z' ]

Append an element before 1st key inside .map() function

I want to recreate whatsapp/telegram-like timeline where messages divided by days.
For rendering messages in React I use Object.key(messages).map function.
Object.keys(this.messages).map(e => {
return <div key={i++}>
{ this.messages[e] }
How to add e.g. 'today' between the last yesterday and first today messages?
I would group all messages into separate arrays, each for each day that occurs. In order to do that, create an object - its keys will be unique days and its values - messages from these days.
// object similar to your 'messages' state
const messages = {
message1: {
body: "one day before message",
time: 1534433188201
},
message2: {
body: "newest message",
time: 1534519588201
},
message3: {
body: "2 days before newest message",
time: 1534346788201
},
message4: {
body: "also 2 days before newest message",
time: 1534346788250
}
};
// creating array from your object
const messagesArray = Object.keys(messages).map(m => messages[m]);
// sorting array - oldest to newest
const latestMessages = messagesArray.sort((a, b) => a.time > b.time);
// grouping by date - create an object, each key is a different date and value is an array of messages from that day
const groupedByDate = {};
latestMessages.forEach(message => {
const date = new Date(message.time);
const day = date.getDate();
const month = date.getMonth();
const year = date.getFullYear();
// this will create 'date_17-7-2018' format as an example - you can do whatever you want/need here
const key = `date_${day}-${month}-${year}`;
// push message to existing key or create new array containing this message
if(groupedByDate[key]) {
groupedByDate[key].push(message);
} else {
groupedByDate[key] = [message];
}
});
console.log(groupedByDate);
The rendering part seems easy now - here's an example how i would approach this:
Map over Object.keys(groupedByDate) and for each key return div or span with a className="date-label"(example). If a day extracted from this key is equal to (new Date()).getDate() - render "today", if it's (new Date()).getDate() - 1 - render "yesterday", otherwise render "X days ago". Now inside this map loop you also need to map over groupedByDate[key] (array of messages from this day) and render messages.
Object.keys(this.messages).map(message => {
const date = new Date();
date.setHours(0,0,0,0); // will set to midnight
const today = Math.round(date.getTime() / 1000);
const timeDifference = this.state.time - today;
const isInRange = timeDifference >= 0 && timeDifference <=86400;
if (isInRange && !this.state.labelAlreadyPresent) {
this.setState({ showLabel: true, labelAlreadyPresent: true });
} else if(!isInRange) {
this.setState({ labelAlreadyPresent: false, showLabel: true });
} else {
this.setState({ showLabel: false });
}
return (
<div key={message.time}>
{this.state.showLabel && <label> Today </label>}
{ message.body }
</div>
);
so basically you first get today's date and set it to midnight. then you get the unix timestamp in seconds. After that you compare the timestamp with the timestamp you received in response and if its within the range of 0 to 86400(equal to 1 day) then you show the Today label.
In the initial state make labelAlreadyPresent and showLabel as false

Traversing an array of objects and adding to other array based on condition

I have an array containing the following objects.
var notTotal = [{"Year":2012,"Value":800579},
{"Year":2012,"Value":654090},
{"Year":2012,"Value":758092},
{"Year":2013,"Value":343928},...More objects.. ]
What im trying to do is traverse this array of objects where only one Year exists instead of multiple and to add up the Values for that year. Using the example above..
var total = [{"Year":2012,"Value":2556689},
//Total of first three 2012 assuming there isnt anymore 2012 in the array
{"Year":2013,"Value":343928},...]
I have tried something like the following:
for(var i = 0; i < notTotal.length; i++) {
if (total.includes(notTotal[i].Year || notTotal[i])) {
//Add the value of the year in notTotal array to the same year in total array
} else {
total.push(notTotal[i]); //add year and value if it does not exist to the total array
}
}
Apologies if this is a duplicate. It seems like a pretty specific question.
Thanks!
An easy solution would be to create an object, holding totals by year.
var totalByYear = {};
You can then loop over the array using notTotal.forEach or a for loop as you've shown, adding to the value of the relevant year inside the totalByYear object.
notTotal.forEach(function(ele) { totalByYear[ele.Year] += ele.Value });
This yields an object with year keys and total values, e.g. using your example:
{'2012': 2556689, '2013': 343928 /* other years */}
The desired format (for D3) can then be built from the totalByYear object (and the totals by year printed):
var totals = [];
for (year in totalByYear) {
console.log('Total for year ' + year + ' is ' + totalByYear[year]);
//Build the correctly formatted array
totals.push({ Year: year, Value: totalByYear[year]});
}
//Prints:
//Total for year 2012 is 2556689
//etc.etc.
The totals array will then have the desired format.
Great question! To explain what's happening in your if (total.includes(notTotal[i].Year || notTotal[i])) is that you are looking through your total array for either just the year, or just an existing notTotal[i] exactly as it is. So your loop is trying to find a value that's exactly 2012 or exactly "Year":2012,"Value":2556689. Ergo, if your total array looked like this:
[{"Year":2012, "Value": 12345}]
your for loop would not find it even though there is an object with 2012 as its year. As for how to fix this, take a look at this previous question!
How to determine if Javascript array contains an object with an attribute that equals a given value?
Hopefully that helps :)
var notTotal = [{"Year":2012,"Value":800579},
{"Year":2012,"Value":654090},
{"Year":2012,"Value":758092},
{"Year":2013,"Value":343928}]
var totalObj = notTotal.reduce((sum, obj) => {
sum[obj.Year] = sum[obj.Year] + obj.Value || obj.Value;
return sum;
}, {});
// convert total to the format you need;
var total = Object.entries(totalObj).map(([Year, Value]) => ({Year, Value}))
console.log(total);
one more solution :
function yearlyValueFilter(array){
var yearlyValue = {}
array.forEach( (obj) => { //litterate on the input
var year = obj.Year
var value = obj.Value
if((year in yearlyValue)){ //if the array with not duplicated years conatins the litteration year just plus that value
yearlyValue[year] += value
}else{ //if not conatins, it gets as a basic value
yearlyValue[year] = value
}
})
return yearlyValue
}
You could built a hash table:
var hash = {},total=[];
for(const {Year,Value} of notTotal){
if(hash[Year]){
hash[Year].Value+=Value;
}else{
total.push(hash[Year]={Year,Value});
}
}
In action
Note: object properties are normally not capitalized...
You could use a hash table and check if the year does not exist, then generate a new result set. Then update the total count.
var values = [{ Year: 2012, Value: 800579 }, { Year: 2012, Value: 654090 }, { Year: 2012, Value: 758092 }, { Year: 2013, Value: 343928 }],
hash = Object.create(null),
totals = [];
values.forEach(function (o) {
hash[o.Year] || totals.push(hash[o.Year] = { Year: o.Year, Value: 0 });
hash[o.Year].Value += o.Value;
});
console.log(totals);

A better way than chaining _.groupBy?

When I do this I get an array which stores the data as grouped by the month and day of the date but not by the year (I am doing this to get maximum, minimum, and average values for each day there is data for)
The problem is that the array stores an array of 2-3 values for that day and month within the date which is the key value. Those 2-3 indices each have an array of length one that holds a reference to an object which has the actual data point (level) I need. The object contains three attributes, date, id (which is always null), and level which is a float.
I either need to find a way so those 2-3 indices hold the object directly, or find a way that _.each can access the level.
Any thoughts?
var groupedData = _.groupBy(data, "date");
var groupedLevels = _.groupBy(groupedData, function (points, date) {
var dateParsed = parseDate(date);
var month = dateParsed.getMonth();
var day = dateParsed.getDate();
var monthDay = month + "-" + day;
return monthDay;
});
_.each(groupedLevels, function (points, date) {
var levels = _.map(_.pluck(points, "level"), parseFloat);
minimum.push([ date, R.min(levels) ]);
maximum.push([ date, R.max(levels);
var averageLevel = R.sum(levels) / levels.length;
average.push([date, averageLevel]);
})
So the data, as is, which is the original input looks like this (a sample piece):
[ { date: "2009-01-01",
id: null,
level: "0.08",
},
// ...
]
Currently, groupedData is this:
{ "2009-01-01":
[ { date: "2009-01-01",
id: null,
level: "0.08"
}
],
// ...
}
groupedLevels looks like this, for example:
{ "0-1":
[ [ { date: "2009-01-01".
id: null,
level: "0.08"
}
],
// ...
],
// ...
}
I want to skip having all the arrays of length one and just have the object stored there.
I think you can fix the immediate issue by replacing this line:
var levels = _.map(_.pluck(points, "level"), parseFloat);
With this:
var levels = _.map(_.pluck(points[0], "level"), parseFloat);
...but I think the real problem might be that you're doing groupBy twice when you don't need to. This single groupBy ought to be equivalent, but without the extra nested array:
var groupedLevels = _.groupBy(data, function(item) {
var dateParsed = parseDate(item.date);
var month = dateParsed.getMonth();
var day = dateParsed.getDate();
return month + '-' + day;
});
With this, your each should work as expected.

Categories

Resources