How to remove element(s) from a state array variable? - javascript

I have a state variable tasks, which is an array of objects:
const [tasks, setTasks] = useState([
{
user: "",
text: "Finish this page",
deadline: new Date("05/01/2022"),
complete: true,
priority: "high",
project: "Gravity",
// ToDo: Set priority based on date while creating task
},
{
text: "Finish Pierian UI/UX",
deadline: new Date("05/10/2022"),
complete: false,
priority: "med",
},
{
text: "Finish Internship",
deadline: new Date("05/05/2022"),
complete: false,
priority: "low",
},
{
text: "Anaadyanta",
deadline: new Date("05/12/2022"),
complete: false,
priority: "med",
},
{
text: "Random task",
deadline: new Date("05/19/2022"),
complete: false,
priority: "high",
},
]);
I want to remove elements whose complete is true. I am unable to think of the correct syntax while using setTasks. Please help.

You should filter tasks and set new state:
const updatedTasks = tasks.filter((el)=> el.complete !== 'true');
setTasks(updatedTasks);

you should pass callback inside setTasks:
setTask((prevTasks) => prevTasks.filter(({ complete }) => !complete));
The prevTasks always going to be the latest state, React insures it. You should always use callback when next state relies on previous.
Also the name of prev variable passed in callback could be whatever you choose, here I named it prevTasks.

Related

Increment ($inc) update not working with upsert (MongoDB, NodeJS)

I am currently working on a MongoDB Post-Hashtag system, I am using NodeJS and the library "Mongoose". Following the creation of a new "post", I wish to increment the quantity of the "posts" field of the given hashtag's document, else if it does not exist: create a new document for the hashtag. I attempted to do this by utilizing the upsert option and performing a single update (updateOne) for each hashtag. The upserting & new document creation process for a new hashtag appears to work fine, however, pre-existing hashtags do not have their "posts" field incremented.
My code:
await Hashtag.updateOne({hashtag: hashtag}, {
$set: {$inc: {posts: 1}},
$setOnInsert: {
uniqueID: hashtagID,
hashtag: hashtag,
timeOfCreation: now,
posts: 1
}
}, {upsert: true});
I have attempted both with & without the '$set' operator, as well as '$update' and '$inc' to no avail. Omitting it results in the following error:
{
"errorType": "MongoServerError",
"errorMessage": "Updating the path 'posts' would create a conflict at 'posts'",
"code": 40,
"index": 0,
"stack": [
"MongoServerError: Updating the path 'posts' would create a conflict at 'posts'",
...
}
My schema as defined in Mongoose:
const hashtagSchema = new mongoose.Schema({
uniqueID: {
type: String,
required: true
},
hashtag: {
type: String,
required: true
},
timeOfCreation: {
type: Number,
required: true
},
posts: {
type: Number,
required: true
},
});
Remove the $set which is used before $inc.
await Hashtag.updateOne({hashtag: hashtag}, {
$inc: {posts: 1},
$setOnInsert: {
uniqueID: hashtagID,
hashtag: hashtag,
timeOfCreation: now,
posts: 1enter code here
}
}, {upsert: true});

How to create nested array of objects using useState react hook

I have a problem finding a solution on how to fill empty nested array by using the useState hook. I'm a react beginner and I have might miss something. In steps below I shall try to summarize the problem.
1.) I receive this data format as props - {question}:
{
category: "General Knowledge"
correct_answer: "Arby's"
difficulty: "easy"
incorrect_answers: (3) ['McDonald's', 'Burger King', 'Wendy's']
question: "In which fast food chain can you order a Jamocha Shake?"
type: "multiple"
}
What my goal output is ty create an object with this structure:
value: "Opel",
isClicked: false,
isCorrect: true,
incorrect_answers: [
{isClicked: false, value: "Bugatti"},
{isClicked: false, value: "Bentley},
{etc...}
]
With this approach I achieve the result, but I would like to find a more correct react way.
useEffect(() => {
const obj = {
value: question.correct_answer,
isClicked: false,
isCorrect: true,
incorrect_answers: []
}
question.incorrect_answers.map((item) =>
obj.incorrect_answers.push({
value: item,
isClicked: false,
})
)
setAnswers(obj)
}, [])
My goal is to have mentioned data structure formed in useState with the right approach on how to access nested arr and fill it with objects.
a) I use the useState for setting up state and its data structure for answers obj.
const [answers, setAnswers] = useState({
value: question.correct_answer,
isClicked: false,
isCorrect: true,
incorrect_answers: [
//I want multiple objects {value: '...', isClicked: ...},
// Would be nice if I was able to insert objects in this a) step.
]
})
b) Perhaps on the useEffect or on some other funcion I want to fill incorect_answers array with objects.
useEffect(() => {
// c) Im accesing the answers state and filling incorect_answers with obj
setAnswers(prevState => {
return {
...prevState,
incorrect_answers: [{
value: question.incorrect_answers.map((item) => item),
isClicked: false,
isCorrect: false
}]
}
})
// d) my output:
/* {
value: "Opel",
isClicked: false,
isCorrect: true,
incorrect_answers: [
{isClicked: false, value: [bugatti, bentley, bmw, citroen]},
]
} */
}, [])
If you're using map you shouldnt ignore the response from it, and you don't need to push to the array
useEffect(() => {
const obj = {
value: question.correct_answer,
isClicked: false,
isCorrect: true,
incorrect_answers: question.incorrect_answers.map(item => ({
value: item,
isClicked: false
}))
}
setAnswers(obj)
}, [])
The same method can be used when first filling your state
const [answers, setAnswers] = useState({
value: question.correct_answer,
isClicked: false,
isCorrect: true,
incorrect_answers: question.incorrect_answers.map(item => ({
value: item,
isClicked: false
}))
})

How do I preserve the order of this javascript array sort?

const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
]
I am trying to sort the array so that the True values come first , then False values.
const orderedModules = modules.sort((a, b) => (a.checked ? -1 : 1))
However, I'd like to preserve the order of the True values. The code above sometimes puts Air first, then Earth (if ran twice). How can I preserve the order all the time?
The callback used as the compare function can return 3 options: negative number, positive number or zero. The negative number indicates that the first parameter should be before the second parameter in the array order. The positive number indicates that the first parameter should be after the second parameter in the array order. And zero means that the order should be kept as is.
Sort array method on MDN
If you want to order just the true values first in the same order in the array and then the false values, probably adding more logic to return zero from the compare function if both are true will solve your issue.
Here is an example:
const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
];
modules.sort((a,b) => a.checked && b.checked ? 0 : a.checked ? -1 : 1);
console.log(modules);
If you don't mind creating a new array, just iterate over the array twice. The first time, push to the new array the objects with the true values as the iteration encounters them. The second time, push the objects with the false values. (JavaScript passes objects by reference so the new array won't cause them to get duplicated.)
Probably this might help you. not sure for optimize way but this function iterate over an array one time only.
I am using reduce function to separate out true and false values and then return them in the order you want.
const shortItems = (array) => {
const orderedModulesObject = array.reduce((orderedModulesObject, currentModule) => {
if(currentModule.checked){
orderedModulesObject.trueValues = orderedModulesObject.trueValues.concat(currentModule);
} else {
orderedModulesObject.falseValues = orderedModulesObject.falseValues.concat(currentModule);
}
return orderedModulesObject;
}, { trueValues: [], falseValues: []});
return orderedModulesObject.trueValues.concat(orderedModulesObject.falseValues);
}
const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
]
console.log(shortItems(modules));
the reason is that sort actually changes the original array. Although modules is defined with const, the values inside can change, as long as you don't assign the variable to something else.
according to this answer, you can sort without mutating the original using spread syntax. this code should work:
const orderedModules = [...modules].sort((a, b) => (a.checked ? -1 : 1))
to make an array or object not able to be modified, you can use Object.freeze()
const modules = Object.freeze([
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
])
Edit: I just realized the order isn't correct, but it at least is the same every time. but that's because the sorting isn't exactly right. here's the correct code:
const orderedModules = [...modules].sort((a, b) => (a.checked != b.checked ? (a.checked ? -1 : 1 ) : 0))

Dynamically push, pull, and set on mongoose schema update

I am trying to setup my patch api so that I can create a dynamic query to push, pull, and set data in my mongoose schema. I have plenty of values that I would change using set, but I also have an array of objects which would require me to call push when I need to insert and pull when I need to remove an item. I'm trying to find the best way to combine this into a dynamic structure.
Schema:
const StepSchema = new Schema({
position: {
type: Number,
required: true
},
name: {
type: String,
required: true
},
due_date: {
type: Date
},
status: [{
label: {
type: String,
enum: ['Inactive', 'In Progress', 'Flagged', 'Complete'],
default: 'Inactive'
},
user: {
type: Schema.Types.ObjectId,
ref: 'users',
},
date: {
type: Date
}
}],
comments: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users',
required: true
},
body: {
type: String,
required: true
},
date: {
type: Date,
required: true
},
}],
});
Api:
router.patch('/',
async (req, res) => {
let setQuery = req.body;
let pushQuery = {};
let pullQuery = {};
//remove id from set query
delete setQuery.id;
//if there is a comment
if(req.body.comment){
pushQuery.comments = req.body.comment
}
//if I need to remove a comment
if(req.body.remove_comment){
pullQuery.comments = {_id: req.body.remove_comment.id}
}
//Push new status into array
if(req.body.status) {
pushQuery.status = {
label: req.body.status,
user: req.user._id,
date: new Date()
};
delete setQuery.status;
}
//update step
await Step.findByIdAndUpdate(req.body.id, {$set: setQuery, $push: pushQuery, $pull: pushQuery})
.then(step => {
if(!step){
errors.noflow = "There was a problem updating the step";
return res.status(400).json(errors);
}
res.json(step)
})
.catch(err => {
console.log(err);
res.status(404).json(err);
});
});
I've been getting the following error when trying to push a new status into my document:
operationTime: Timestamp { bsontype: 'Timestamp', low: 1, high_:
1560978288 }, ok: 0, errmsg: 'Updating the path \'status\' would
create a conflict at \'status\'', code: 40, codeName:
'ConflictingUpdateOperators', '$clusterTime': { clusterTime:
Timestamp { bsontype: 'Timestamp', low: 1, high_: 1560978288 },
signature: { hash: [Object], keyId: [Object] } },
Oh, you're doing that $set and $push on a status. Your pushQuery is trying to have status be an array on the document, and your setQuery wants to set it to whatever it was on the actual body (I'm guessing the same object.
A quickfix would be to remove it from the set object:
delete setQuery.status
A reasonable and stable way to do this would be to actually only take the things from req.body which you really want for each of the stages. Example:
const { position, name, dueDate, status, comment, remove_comment } = req.body;
const setQuery = { position, name, dueDate };
const pushQuery = { status, comments: comment };
// ...
That way your queries are not conflicting in any way.

unable to update value within a Javascript object literal, and then update the page

I've tried around 10 answers to other questions here but nothing seems to work. I've tried each and map and grep.
I want to change a value in an object literal and then update my list.
Here's the array:
goals = [
{
goalguid: "691473a7-acad-458e-bb27-96bac224205b",
category: "personal",
datecreated: "2013-10-20",
startdate: "2013-10-28",
enddate: "2013-11-26",
goal: "go to store",
icon: "cart",
status: "completed",
username: "jtmagee",
userguid: "0c7270bd-38e8-4db2-ae92-244de019c543"
}, {
goalguid: "9e693231-e6d8-4ca9-b5c8-81ea7a80a36a",
category: "personal",
datecreated: "2013-10-20",
startdate: "2013-10-27",
enddate: "2013-11-27",
goal: "Brush Teeth",
icon: "doctor",
status: "inprogress",
username: "jtmagee",
userguid: "0c7270bd-38e8-4db2-ae92-244de019c543"
}, {
goalguid: "8d23005d-f6f3-4589-bb85-a90510bccc21",
category: "work",
datecreated: "2013-10-20",
startdate: "2013-10-26",
enddate: "2013-11-28",
goal: "Arrive on Time",
icon: "alarm",
status: "inprogress",
username: "jtmagee",
userguid: "0c7270bd-38e8-4db2-ae92-244de019c543"
}, {
goalguid: "ac879673-19eb-43f6-8b95-078d84552da0",
category: "school",
datecreated: "2013-10-20",
startdate: "2013-10-24",
enddate: "2013-11-29",
goal: "Do Math Homework",
icon: "book",
status: "missed",
username: "jtmagee",
userguid: "0c7270bd-38e8-4db2-ae92-244de019c543"
}
];
I'm keying off the goalguid gotten from the li that the user has clicked. I use the status key to change classes on the li.
Here's my jQuery that catches the click on a checkbox:
$(".goals").delegate("input[type=checkbox]", "click", function() {
goalguid = $(this).parent().parent().data("goalguid");
emailGoal = $(this).parent().data('goal');
if ($(this).prop("checked")) {
$(this).parent().parent().removeClass("inprogress missed").addClass("completed").prop("checked", true);
updateStatus = 'completed';
return updateTheGoal();
} else {
$(this).parent().parent().removeClass("completed").addClass("inprogress").prop("checked", false);
updateStatus = 'inprogress';
return updateTheGoal();
}
});
Here's what I can't get to work. Some attempts cleaned out the array completely, but most, including this, do nothing except make my list flash. The displayGoalList function works fine for other uses.
updateTheGoal = function() {
var goalList;
$.each(goals, function() {
if (goals.goalguid === goalguid) {
return goals.status === updateStatus;
}
});
goals = JSON.parse(localStorage["goals"]);
goalList = $.grep(goals, function(e) {
return e.userguid === userguid;
});
localStorage.setItem(userguid + "Goals", JSON.stringify(goalList));
logSummary();
return displayMyGoalList();
};
Any help is much appreciated. Thanks.
$.each(goals, function() {
if (goals.goalguid === goalguid) {
return goals.status === updateStatus;
}
});
You are checking goals.goalguid and setting goals.status. This is wrong. goals is an array. Your intent is to check each element in the array. Then you aren't actually doing anything with your return. return within each simply breaks the loop or continues it (depending on return value). I think you meant to assign it. Try this:
$.each(goals, function(i, el) {
if (el.goalguid === goalguid) {
el.status = updateStatus;
return false; // break the each
}
});
Also, remove this bit that will replace any change made by your each with whatever is stored in local storage:
goals = JSON.parse(localStorage["goals"]);

Categories