Check DB Array for Duplicate Key on Form Submit - javascript

This is a MERN stack app. I have a "WeekMealPlans" collection and "Days" collection, where each Day must have a WeekMealPlan and a "DayOfWeek", as in "Friday," and it has a name that combines WeekMealPlan and DayOfWeek like "Week 1 - Monday."
I want to prevent a duplicate combo of WeekMealPlan + DayOfWeek, so in my Day Mongoose Model, "name" has attribute "unique."
In React, I set a Days array in State with an Axios call, so all Days are available to check for duplicates on form submit.
On form submit, I just want to take the generated "Day Name," check it against the Days array for a duplicate, and alert the user if a dup is found.
I've tried an If Statement inside a For Loop, as well as Array.find. Both do not seem to work per the below. Do If Statements, For Loops and Array.find, as well as Undefined variable not work the same in React as in Vanilla JS?!
//Here's the Days Array:
[
{
_id: "609f3e444ee536749c75c72b",
dayOfWeek: "Monday",
weekMealPlan: {
_id: "609f3e444ee536749c75c72a",
name: "JD Hypertrophy Week 1",
GRFUser: "609f3e444ee536749c75c729",
createdAt: "2021-05-15T03:21:40.285Z",
updatedAt: "2021-05-15T03:21:40.285Z",
__v: 0,
},
__v: 0,
},
{
_id: "610dbb89bebaea6004ce9f53",
dayOfWeek: "Sunday",
weekMealPlan: {
_id: "609f3e444ee536749c75c72a",
name: "JD Hypertrophy Week 1",
GRFUser: "609f3e444ee536749c75c729",
createdAt: "2021-05-15T03:21:40.285Z",
updatedAt: "2021-05-15T03:21:40.285Z",
__v: 0,
},
createdAt: "2021-08-06T22:45:29.826Z",
updatedAt: "2021-08-06T22:45:29.826Z",
__v: 0,
},
{
_id: "622ac86263a8575ecb8c0f5e",
name: "JP Nash's WMP - Friday",
dayOfWeek: "Friday",
weekMealPlan: {
_id: "62283f3d398c00aee52b7e99",
name: "JP Nash's WMP",
GRFUser: "62283f21398c00aee52b7e93",
createdAt: "2022-03-09T05:46:37.756Z",
updatedAt: "2022-03-09T05:46:37.756Z",
__v: 0,
},
createdAt: "2022-03-11T03:56:18.136Z",
updatedAt: "2022-03-11T03:56:18.136Z",
__v: 0,
},
];
//For Loop method:
let isDayDup = false;
const daysArray = this.state.days;
const dayName = this.state.name;
let i = 0;
{
for (i = 0; i < daysArray.length; i++) {
if (daysArray[i].name == dayName) {
isDayDup = true;
}
}
}
console.log(isDayDup);
//No matter whether the passed Day Name is or is not a duplicate, the result is "false"!
//Array.find method:
const daysArray = this.state.days;
const dayName = this.state.name;
function findDayDup(thisDay) {
return thisDay.name == dayName;
}
const dupDay = daysArray.find(findDayDup);
if (dupDay == undefined) {
console.log("Duplicate Day Name!");
} else {
console.log("Day is OK!");
}
//No matter whether the passed Day Name is or is not a duplicate, the result is "Duplicate Day Name"!

Related

Unable to find item in this array when I do it outside of the map function it works inside it doens't

I am trying to make a calendar with events that are rendered dynamically.
I use this to create the days for the calendar and create an array to display. I try to create an events object containing the details for the events. When I try to outside of the initialDays map fuction it works fine and is able to find the object with the correct date and time however when I try to use find function inside of the initialDays map function I get this error.
events.find((item) => {
return item.datetime == dates
})
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'length')
createDays()
{
this.today = startOfToday()
this.formatedDate = format(this.today, 'MMM, yyyy')
this.currentMonth = this.formatedDate
this.getNewDays = parse(this.currentMonth, 'MMM, yyyy', new Date())
this.firstDayCurrentMonth = parse(this.currentMonth, 'MMM, yyyy', new Date())
let initialDays = eachDayOfInterval({
start: startOfWeek(this.getNewDays, {weekStartsOn: 1}),
end: endOfWeek(endOfMonth(this.getNewDays), {weekStartsOn: 1})
})
let events = [{ id: 3, name: 'Date night', time: '6PM', datetime: '2022-10-10', href: '#' }, { id: 4, name: 'Date night', time: '6PM', datetime: '2022-10-12', href: '#' }, { id: 3, name: 'Date night', time: '6PM', datetime: '2022-10-13', href: '#' }]
events = events.map((events) => {
return {
id: events.id,
name: events.name,
time: events.time,
datetime: events.datetime,
href: events.href
}
})
const findevent = events.find((item) => {
return item.datetime === '2022-10-10'
})
console.log(findevent)
initialDays = initialDays.map((dates) => {
return {
date: format(dates, 'yyyy-MM-dd'),
isCurrentMonth: isSameMonth(this.getNewDays, dates),
isToday: isToday(dates),
isSelected: isSameDay(dates, this.today),
events: events.find((item) => {
return item.datetime == dates
})
}
})
this.days = initialDays
},
EDIT ***
InitialDays returns a array of dates from start of the current month to end of current month plus 1 week before and 1 week after.
I want the events to be filled in if they match dates. However currently it just fills every date with the all the events. I thought using the find method would work. Date-fns has a ```isSameDay`` function however it only returns true or false. Not sure how to continue...
{
"date": "2022-10-22",
"isCurrentMonth": true,
"isToday": false,
"isSelected": false,
"events": [
{
"id": 3,
"name": "Date night",
"time": "6PM",
"datetime": "2022-10-10",
"href": "#"
},
{
"id": 4,
"name": "Date night",
"time": "6PM",
"datetime": "2022-10-12",
"href": "#"
},
{
"id": 3,
"name": "Date night",
"time": "6PM",
"datetime": "2022-10-13",
"href": "#"
}
]
}
First, let's see the events you are working with:
let events = [{ id: 3, name: 'Date night', time: '6PM', datetime: '2022-10-10', href: '#' }, { id: 4, name: 'Date night', time: '6PM', datetime: '2022-10-12', href: '#' }, { id: 3, name: 'Date night', time: '6PM', datetime: '2022-10-13', href: '#' }]
The items are objects and
const findevent = events.find((item) => {
return item.datetime === '2022-10-10'
})
is working for you, which proves that this is operational.
In the snippet below we can see that map does not work when you want to call it for an object
let initialDays = {
start: new Date(),
end: new Date()
};
initialDays = initialDays.map((dates) => {
return dates.length
})
therefore eachDayOfInterval returns an array (otherwise you would get an error, complaining that map is not a function). So, your function of eachDayOfInterval does something that you did not share, so it's unclear what this array will contain.
However, it is very safe to assume that the eachDayOfInterval returns an array of days (in whatever format). When you do something like
initialDays.map((dates) => {
//...
}
you therefore will iterate your dates and each iteration is a date (day). So the naming of dates above shows that you misunderstood this part and you assumed that you will have an array of dates inside the map callback, but you will have an individual date inside that callback instead, looping through the whole array.
The error suggests that you tried to read the property of length on undefined. This proves the following:
your dates has at least one undefined element, which is the technical reason you get this error
you treat the individual date as if it was an array of dates
items.map(item => something(item))
will loop items and perform a mapping for each item.

mongoDB find most popular posts within a week from the current date

I have a controller where I am trying to query the most popular posts within the last week, sorted by most popular, and has a max cap of 50 posts. I am trying to use the aggregate() method; however, I am not sure if I am doing it correctly. When I run the query In insomnia I get an error like so:
{
"ok": 0,
"code": 8000,
"codeName": "AtlasError"
}
Here is my post model:
const postSchema = mongoose.Schema({
title: {
type: String,
required: true
},
message: {
type: String,
required: true
},
//replace creator with name
name: String,
creator: String,
tags: [String],
size: String,
selectedFile: String,
likes: {
type: [String],
default: [],
},
comments: {
type: [String],
default: []
},
createdAt: {
type: Date,
default: new Date(),
},
dogTreats: {
type: Number,
default: 0,
required: false,
}
});
and here is my controller/post.js
export const getPopular = async (req, res) => {
//get current time
let currentTime = new Date()
//get from 7 days ago
currentTime.setDate(currentTime.getDate()-7)
console.log(currentTime) // -> output 2022-09-04T19:29:39.612Z
try {
//sort posts by most likes and within 7 days ago, but with a max of 50 posts
const mostPopular = await PostMessage.aggregate([{"$sort": { likes: -1}}, { "$limit": 50}, { "$gt": currentTime }])
res.status(200).json(mostPopular)
} catch (error) {
res.status(500).json(error)
}
}
You can use find method. It is better to use here.
If you need to reach a value from another table populated, aggregation is better to use. However, at here, find is the best way to reach datas.
const mostPopular = await PostMessage.find({createdAt: {$gt : currentTime}}).sort({likes: -1}).limit(50)
Try this aggregation
export const getPopular = async (req, res) => {
//get current time
let currentTime = new Date()
//get from 7 days ago
currentTime.setDate(currentTime.getDate() - 7)
console.log(currentTime) // -> output 2022-09-04T19:29:39.612Z
try {
//sort posts by most likes and within 7 days ago, but with a max of 50 posts
const mostPopular = await PostMessage.aggregate([
{ $match: { createdAt: { $gt: currentTime } } },
{ $sort: { likes: -1 } },
{ $limit: 50 }
])
res.status(200).json(mostPopular)
} catch (error) {
res.status(500).json(error)
}
}

Dynamically split an array into subarrays

const giftcards = [
{
fromuserid: 1,
amount: 10,
date: new Date(),
touserid: 11,
},
{
fromuserid: 1,
amount: 20,
date: new Date(),
touserid: 11,
},
{
fromuserid: 2,
amount: 10,
date: new Date(),
touserid: 11,
},
{
fromuserid: 2,
amount: 10,
date: new Date(),
touserid: 11,
},
{
fromuserid: 3,
amount: 10,
date: new Date(),
touserid: 11,
}
]
I achieved this, which is shown in the useEffect hook:
const giftcards = [
{
fromuserid: 1,
amounts: [{
amount: 10,
date: new Date(),
touserid: 11,
},
{
amount: 20,
date: new Date(),
touserid: 11,
}
]
},
{
fromuserid: 2,
amounts: [{
amount: 10,
date: new Date(),
touserid: 11,
},
{
amount: 10,
date: new Date(),
touserid: 11,
}
]
},
{
fromuserid: 3,
amounts: [{
amount: 10,
date: new Date(),
touserid: 11,
}]
}
]
The solution that was given works except that i would like to make it dynamic.
Meaning, in my app, I allow the user to arrange how the array will be sorted.
For example,
const [sort, setSort] = useState('fromuserid')
const [results, setResults] = useState([])
<div>
<select value={sort} onChange={(e)=> setSort(e.target.value)}>
<option value='fromuserid'>Sort by Buyer</option>
<option value='touserid'>Sort by Receiver</option>
<option value='date'>Sort by Date</option>
</select>
</div>
then in the:
useEffect(() => {
allgiftcards.forEach(({
fromuserid,
date,
from,
giftcardid,
message,
template,
touserid,
amount,
paid
}) => {
map.has(fromuserid) || map.set(fromuserid, {
fromuserid,
cards: []
})
map.get(fromuserid).cards.push({
date,
from,
giftcardid,
message,
template,
touserid,
amount,
paid
})
})
setResults([...map.values()])
}, [sort])
Here is what i mean by dynamic. If the user selected date, I would like for it to look something like:
useEffect(() => {
allgiftcards.forEach(({
fromuserid,
date,
from,
giftcardid,
message,
template,
touserid,
amount,
paid
}) => {
map.has(date) || map.set(date, {
date,
cards: []
})
map.get(date).cards.push({
date,
from,
giftcardid,
message,
template,
touserid,
amount,
paid
})
})
setResults([...map.values()])
}, [sort])
But It seems to me that having a bunch of if and else statements would be bad coding and would create a lot of extra code, so looking for a nice and clean solution
This really isn't a question of React (or where-ever useEffect comes from). This is really a general Javascript question, and it's a problem that suits itself well for solving with functional programming, or at least, with one of the staples of functional programming: reduce
In this case, you could supply the 2nd argument which is the initial value of the accumulator -- in this example an empty object works well. You can choose any key from the data to bucket the results:
// Choose a key that each object in the set has, e.g. 'fromuserid' or 'touserid'
const group_by = 'fromuserid';
let bucketed = giftcards.reduce(function (acc, x) {
let pivot = x[group_by]
let current_vals = (acc.hasOwnProperty(pivot) ? acc[pivot] : []);
current_vals.push(x);
acc[pivot] = current_vals;
return acc
}, {});
console.log(bucketed);
If you really needed to land on the second data structure you shared, you could jostle your initialValue and the exact placement of values into the accumulator, but hopefully this demonstrate the concept of how to dynamically choose how to group the data.
This codepen shows how to dynamically bucket an array of objects based on a specific property. Below is a sample of the callback function you can pass to the reducer -- included in the example.
function groupBy(accum, current, groupKey){
const val = current[groupKey]
const {[groupKey]:_, ...content} = current
if (val in accum){
accum[val].push(content)
}
else accum[val] = [content]
return accum
}

How to create a json grouped by date in js?

I have an array of objects sorted by date:
const alerts = [{
id: 1, date: '2018-10-31T23:18:31.000Z', name: 'Joke', title: 'this is the first 1'
}, {
id: 2, date: '2018-10-30T23:18:31.000Z', name: 'Mark', title: 'this is the second one'
}]
I am trying to 'group' the alerts by date so trying to create 'datesections' which have a dateheader, the result should be something like:
const sections = [{
date: '2018-10-31T23:18:31.000Z',
heading: 'today',
alerts: [{ id: 1, date: '2018-10-31T23:18:31.000Z', name: 'Joke',
title: 'this is the first one' }]
}, {
date: '2018-10-30T23:18:31.000Z',
heading: 'Yesterday',
alerts: [{ id: 2, date: '2018-05-30T23:18:31.000Z', name: 'Mark',
title: 'this is the second one' }]
}]
I tried something this but can't figure out how to get the alerts with the same date in the alerts prop:
const sections2=alerts.map(a =>
({
date: a.date,
heading:'today new',
alerts:alerts
})
)
const alerts = [
{ id: 1, date: '2018-10-31T23:18:31.000Z', name: 'Joke', title: 'this is the first 1' },
{ id: 2, date: '2018-05-30T23:18:31.000Z', name: 'Mark', title: 'this is the second one' }
]
const grouping = _.groupBy(alerts, element => element.date.substring(0, 10))
const sections = _.map(grouping, (items, date) => ({
date: date,
alerts: items
}));
console.log(sections);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Can't help you with headings - what if it's neither "today" or "yesterday"?
I feel like you are asking a couple of things here. The key one is how to group by day with a date.
To do that you will first need to know how to group. This answer may help with that.
As far as how to group by day there are a number of ways to do that. Simplest I can think of is to cut off everything after the "T" in the date string and sort that.
From my point of view it's not really a map what you need here, map will return a new array but not what you want. You can do this with 2 for statements
let total = [];
for (let j = 0; j < alerts.length; j++) {
let item = alerts[j];
let foundDate = false;
for (let i = 0; i < total.length; i++) {
if (total[i].date === item.date) {
foundDate = true;
total.alerts.push(item);
}
}
if (!foundDate) {
console.log("!found");
total.push({
date: item.date,
heading: "Yesterday",
alerts: [item]
});
}
}
If you console.log yout total array, will contain what you want.
If you need any other explanation pls let me know.
You can use a regular expression to match the part of the date you want and then group your data. You can add there the header you want. Hope this helps.
const alerts = [
{ id: 1, date: '2018-10-31T23:18:31.000Z', name: 'Joke', title: 'this is the first 1' },
{ id: 2, date: '2018-10-30T23:18:31.000Z', name: 'Mark', title: 'this is the second one' },
{ id: 3, date: '2018-10-30T23:14:32.000Z', name: 'Mark', title: 'this is the third one' }
];
const groupByDate = (data) => {
return data.reduce((acc, val) => {
const date = val.date.match(/\d{4}-\d{2}-\d{2}/g).toString();
const item = acc.find((item) => item.date.match(new RegExp(date, 'g')));
if (!item) acc.push({ date: val.date, alerts: [val], heading: 'some heading' });
else item.alerts.push(val);
return acc;
}, []);
};
console.log(groupByDate(alerts));
Maybe you need something like this? Didn't have much time for this and last array parsing might be done in more elegant way ;)
var alerts = [
{ id: 1, date: '2018-10-31T23:18:31.000Z', name: 'Joke', title: 'this is the first 1' },
{ id: 3, date: '2018-10-31T23:44:31.000Z', name: 'Joke1', title: 'this is the 2nd' },
{ id: 2, date: '2018-10-30T23:18:31.000Z', name: 'Mark', title: 'this is the second one' },
{ id: 4, date: '2018-10-30T23:45:31.000Z', name: 'Mark1', title: 'this is the 3rd' },
{ id: 5, date: '2018-10-27T23:18:31.000Z', name: 'Mark2', title: 'this is the 4th' },
];
var processedAlerts = [], finalAlerts;
(function(initAlerts){
//iterate through array to make keys to group by day
for(var i = 0; i < initAlerts.length; i++){
processedAlerts[i] = initAlerts[i];
//substring here can be more sophisticated - this was faster
initAlerts[i].keyDate = initAlerts[i].date.substr(0, 10);
}
//supporting function to convert string to date
//to acheve more detailed sorting that includes time
//just use date object and use hours, minutes and/or seconds to create Date object
function dateFromString(strDate){
var date, tmpDate;
//convert string to array - I assume that date format is always the same
//yyyy-mm-dd and will become Array 0: year, 1: month, 2: day of the month
tmpDate = strDate.split("-");
//moths in js are zero pased so Jan is 0, Feb is 1 and so on
//so we want to substract 1 from human readable month value to get correct date
date = new Date(tmpDate[0], tmpDate[1]-1, tmpDate[2]);
return date;
}
//function used to compare dates and passed to sort function
function comparedates(obj1, obj2){
var date1, date2;
date1 = dateFromString(obj1.keyDate);
date2 = dateFromString(obj2.keyDate);
let comparison = 0;
if(date1>date2){
comparison = 1;
} else if(date1<date2){
comparison = -1;
}
//to achieve reverse just multiply comparison result by -1
return comparison*(-1);
}
function getHeader(date){
//here place logic to generate header
//this involves comparing dates probably from keyDate
return "temp header: " + date.toString()
}
//sort the array by keyDate from newest to oldest
processedAlerts.sort(comparedates);
//final array rebuild
//pass here sorted array
finalAlerts = (function(arrayAlerts){
var aAlerts = [], k = 0;
for(var j = 0; j < arrayAlerts.length; j++){
//check if entry for date exists
//if no than create it
if(!aAlerts[k]){
aAlerts[k] = {
//removed title because I asummed that each alert has unique title and put them in alerts instead
date: arrayAlerts[j].keyDate, //agroupped date
heading: getHeader(arrayAlerts[j].keyDate), //update this function to return personalized heading
//here you can shape the alert object how you need
//I just passed it as it was
alerts: [arrayAlerts[j]] //array with first object inside
};
} else {
//add another alert to day
aAlerts[k].alerts.push(arrayAlerts[j]) //array with first object inside
}
//increasing final array key
//if there is previous entry and keys are the same for current and previous
if(arrayAlerts[j-1] && (arrayAlerts[j].keyDate == arrayAlerts[j-1].keyDate)){
k++;
}
}
return aAlerts;
})(processedAlerts);
})(alerts);
console.log(finalAlerts);

Ambiguous Unexpected Identifier error

Mongo isn't liking some identifier that I've got in the forEach loop holding the second aggregation, and for the life of me I can't find which one it is. I've been looking at it all day and at this point I just need another pair of eyes on it. My eyes, brain, and heart thank you in advance!
use events
var affected = []
var start = new Date()
var end = new Date("2017-06-01T00:00:00Z")
for (var dayPast = new Date(start); start >= end; start.setDate(start.getDate() - 1)) {
dayPast.setDate(dayPast.getDate() - 1)
var results = db.completion_event.aggregate([{
$match: {
applicationId: 1,
dateCreated: {
$lt: start,
$gte: dayPast
},
"data.name": "eventComplete",
"data.metadata.aggregationId": /identifying_string.*/,
"data.sponsorIds": {$in: [1,2,3,4,5,6]}
}
}, {
$project: {
customerId: 1,
dateCreated: 1,
"data.metadata.aggregationId": 1
}
}, {
$group: {
_id: {
customerId: "$customerId",
dateCreated: "$dateCreated",
aggregationId: "$data.metadata.aggregationId"
},
"total": {
$sum: 1
}
}
}], {
$limit: 1
}, {
allowDiskUse: true
}).toArray()
results.forEach(function(event) {
use rewards
var state = db.customer_state.find({customerId: event._id.customerId}).sort({_id: -1}).limit(1).toArray()[0]
var planId = state.planId
var plan = db.plan.find({id: planId}).toArray()[0]
if(plan.schedule.activeStart < new Date() < plan.schedule.activeEnd) {
use events
var latest = db.completion_event.aggregate([{
$match: {
applicationId: 1,
customerId: event._id.customerId,
dateCreated: {
$gte: plan.schedule.activeStart
},
"data.name": "outterEventComplete",
"data.metadata.aggregationId": event._id.aggregationId
}
},
{
$project: {
consumerId: 1,
dateCreated: 1,
"data.sponsorIds": 1,
"data.metadata.aggregationId": 1
}
}], {
$limit: 1
}).toArray()
affected.push(latest[0])
}
})
}
print(affected)
And the current bane of my existence:
E QUERY SyntaxError: Unexpected identifier
I'm betting on use rewards and use events. Those are shell shortcuts, you're not supposed to use them in the middle of regular javascript code. Here's an alternative:
Instead of switching db via use rewards use this
var rewards_db = db.getSisterDB('rewards');
rewards_db.customer_state.find(...)
Same for events, naturally.

Categories

Resources