ReactJS + Axios + NodeJS - _id: Cannot read property 'ownerDocument' of null - node.js

I have this ReactJS app connected by Axios to a backend in Node. I'm trying to update, and the payload is correct, but I have an awkward problem: it says I'm not sending the _id that I need to be updated. Here is my mongoose Schema, the request in Axios and the express backend method for it.
Axios request:
submit () {
let data = this.state.category
axios({
method: this.state.category._id ? 'put':'post',
url: `/category/${this.state.category._id || ''}`,
data: data
})
.then(res => {
let list = this.state.categoryList
list.push(res.data.category)
this.update({
alert: {
type: "success",
text: "Category updated"
},
categoryList: list
})
this.toggleLarge()
})
.catch(e => {
this.update({
category: {
errors: e.errors
},
alert: {
type: "danger",
text: "Error",
details: e
}
})
})
}
Mongoose Schema:
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
let Schema = mongoose.Schema;
let categorySchema = new Schema({
description: {
type: String,
unique: true,
required: [true, 'Category required']
}
});
categorySchema.methods.toJSON = function() {
let category = this;
let categoryObject = category.toObject();
return categoryObject;
}
categorySchema.plugin(uniqueValidator, { message: '{PATH} must be unique' });
module.exports = mongoose.model('Category', categorySchema);
Express method:
app.put('/category/:id', [verifyToken], (req, res) => {
let id = req.params.id;
Category.findByIdAndUpdate(id, req.body, { new: true, runValidators: true }, (err, categoryDB) => {
if (err) {
return res.status(400).json({
ok: false,
err
});
}
res.json({
ok: true,
category: categoryDB
});
})
});
Request payload:
{"description":"Saladitos","errors":{},"_id":"5e5940dd7c567e1891c32cda","__v":0}
And the response:
"Validation failed: _id: Cannot read property 'ownerDocument' of null, description: Cannot read property 'ownerDocument' of null"

This is the contract of findByIdAndUpdate:
A.findByIdAndUpdate(id, update, options, callback)
Your update object is req.body which contains _id. Am guessing that it will try to update _id as well, which should not happend.
Try to specify which columns you want to update
Model.findByIdAndUpdate(id, { description: req.body.description, ... }, options, callback)
Hope this helps.

I can see you might not change the ObjectId:_id into other names. But for people who had done that and see a similar problem, Check this. https://liuzhenglai.com/post/5dbd385f8dea5b6b578765d9
So you probably need to change your code into this
const id = request.params.id
Model.findByIdAndUpdate(
id,
{id:id ,description: req.body.description, ... },
{runValidators: true, context: 'query'},
callback
)

You don't have to say {id: id} just pass context is equal to query like this
{runValidators: true, context: 'query'}

Related

Mongoose auto-increment fails because of a cast error

I am trying to increment a simple number field, but it is telling me it is failing to to a casting error.
CastError: Cast to Number failed for value "{ '$inc': 1 }" (type Object) at path "times_dealt"
Says it's an object?
This is my schema for Answer
const answerSchema = new mongoose.Schema({
body: {
type: String,
trim: true,
required: true,
},
times_dealt: {
type: Number,
required: true,
},
times_picked: {
type: Number,
required: true,
},
times_won: {
type: Number,
required: true,
},
}, {
timestamps: true,
});
module.exports = { answerSchema };
This is my route for me the admin to add new answers (it's a game so only I can add them, that why the auth. Figured I'll include the complete code.)
router.post("/answers", async(req, res) => {
try {
const isMatch = await bcrypt.compare(
req.body.password,
process.env.ADMIN_PASSWORD
);
if (isMatch) {
const answer = new Answer({
body: req.body.answer.trim(),
times_dealt: 0,
times_picked: 0,
times_won: 0,
});
await answer.save();
res.status(201).send(answer);
}
res.status(401).send();
} catch (e) {
console.log("failed to save", e);
res.status(400).send(e);
}
});
Then whenever a card is dealt, I want to increase the count for times_dealt, and this is when I get the error. This is how I do it:
async function getOneAnswerCard(room) {
if (room.unused_answer_cards.length !== 0) {
// We pick a random answer's ID from our array of unused answers
const randomAnswerID = getRandomElement(room.unused_answer_cards);
// We get that answer's full object from our DB
const newAnswer = await Answer.findById(randomAnswerID);
// const newAnswer = await Answer.findByIdAndUpdate(randomAnswerID, {
// times_dealt: { $inc: 1 },
// });
await Answer.findByIdAndUpdate(randomAnswerID, {
times_dealt: { $inc: 1 },
});
// We remove it from the unused cards array
room.unused_answer_cards = room.unused_answer_cards.filter(
(answerID) => answerID !== randomAnswerID
);
// We add it to the dealt cards array
room.dealt_answer_cards.push(randomAnswerID);
// We serialize the answer (we don't want the user to get info on our answer stats)
const serializedAnswer = { _id: newAnswer._id, body: newAnswer.body };
return serializedAnswer;
}
}
Just getting the answer by itself is no issue. Getting a random ID and fetching an answer object works just fine. It's only when I've added the increment functionality that it started crashing.
I think you're using $inc with a wrong syntax. Try this:
await Answer.findByIdAndUpdate(randomAnswerID, {
{ $inc: { times_dealt: 1 } },
});

Error: Cast to undefined failed for value undefined at path "undefined"

I am a beginner JavaScript developer.
I am getting this error when I try to get all soldiers by route: "localhost:3005/api/v1/soldiers" in POSTMAN.
I wrote schema for HomePost, services, controllers and routes and used mongoose, express. When sending any requests, everything worked fine. Find by ID, delete by ID, create home post, get home posts list.
I started writing a similar schema, services, controllers and routes for soldiers.
models/sordier.model.js
import mongoose from 'mongoose'
const Soldier = new mongoose.Schema({
title: {
type: String,
required: true
},
image: {
type: String,
required: true
},
rank: {
type: String,
required: true
},
status: {
type: String,
default: '1'
},
created_at: {
type: Date,
default: new Date()
},
updated_at: {
type: Date,
default: new Date()
}
})
export default mongoose.model('Soldier', Soldier)
services/soldiers.service.js
export const getSoldiers = async ({
offset = 0,
limit = 9
}) => Soldier.find({}, null, {
skip: parseInt(offset),
limit: parseInt(limit)
})
api/soldiers/soldiers.controller.js
export const SoldiersListController = async (request, response, next) => {
try {
const { offset, limit } = request.query
const soldiers = await getSoldiers({
offset,
limit
})
return response
.status(200)
.json({
status: true,
soldiers: Array.isArray(soldiers)
? soldiers?.map(soldier => SoldiersListItemMapper(soldier))
: []
})
} catch (error) {
console.log(error);
next(error)
}
return response
.status(200)
.json({
success: true,
data
})
}
api/soldiers/soldiers.mapper.js
export const SoldiersListItemMapper = soldier => ({
id: soldier._id,
title: soldier.title,
image: soldier.image,
rank: soldier.rank,
})
api/index.js
import { Router } from 'express'
import HomeRoute from './home'
import SoldiersRoute from './soldiers'
export default () => {
const router = Router()
HomeRoute(router)
SoldiersRoute(router)
return router
}
Using POSTMAN, I made sure that the function works:
create a soldier, get one by ID, delete one by ID, but getting a list of soldiers does not work.
I am catching the error:
Error: Cast to undefined failed for value undefined at path "undefined"
What can I do?

Is there a way to get data from db before update for MongoDb - Mongoose

I want to do an update method for my project but i couldn't solve this issue. My model has a field call slug. If I need I add data to make this value unique. However, i am using findByIdAndUpdate method on my update function. I am wondering about that is there a way to get data before update this model? Do I have to make at least 2 different requests to my db to get the old data or does this method I use give me a chance to compare data? Because if the title field has changed I need to compare it and generate the new slug value.
Category model
const mongoose = require('mongoose')
const CategorySchema = mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
minLength: 3,
maxLength: 70
},
description: {
type: String,
requried: true,
trim: true,
minLength: 30
},
coverImage: {
type: String,
trim: true
},
slug: {
type: String,
unique: true,
required: true
}
}, {collection: "categories", timestamps: true})
module.exports = mongoose.model('category', CategorySchema);
Update function
const update = async (req, res, next) => {
delete req.body.createdAt;
delete req.body.updatedAt;
try {
const data = req.body;
data.coverImage = req.file ? req.file.path.replace(/\\/g, "/") : undefined;
data.slug = data.title ? slugCreator(data.title, null): undefined;
const result = await CategoryModel.findByIdAndUpdate(req.params.categoryId, data, { new: true, runValidators: true });
if (result) {
return res.json({
message: "Category has been updated",
data: result
});
}else{
throw createError(404, "Category not found.")
}
} catch (error) {
next(createError(error));
}
};
You could solve your problems first by getting the documents and then do the update with the save method like the following example
const update = async (req, res, next) => {
delete req.body.createdAt;
delete req.body.updatedAt;
try {
const data = req.body;
//here you have the current category
const category = await CategoryModel.findById(req.params.categoryId);
if (!category) {
throw createError(404, 'Category not found.');
}
//do all you comparation and setting the data to the model...
category.slug = data.title ? slugCreator(data.title, null) : undefined;
category.coverImage = req.file
? req.file.path.replace(/\\/g, '/')
: undefined;
await category.save();
return res.json({
message: 'Category has been updated',
data: category,
});
} catch (error) {
next(createError(error));
}
};

I'm unable to push my array to mongodb document using node.js

In my previous html code when I submit it sends a post to /comment/:id then the website crashes and outputs MongoError: Unsupported projection option: $push: { comment: { content: "gfdghd" } } in my console. I don't know how to solve it and I hope I can get some help on the issue as I'm a starter with web development.
I want this to work by pushing the array which includes the req.body into a certain mongodb array default collection where it finds the parent post _id. If you need me to elaborate please ask, thanks.
This is my code:
app.js
const Post = require("./models/Post");
mongoose
.connect("secret", {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: true,
})
.then(() => {
console.log("connected to mongodb cloud! :)");
})
.catch((err) => {
console.log(err);
});
app
.post("/comment/:id", authenticateUser, async (req, res) => {
const content = req.body;
// checks for missing fields
if (!content){
return res.send("Please enter all the required credentials!");
}
//This is where I tried to match and then push it to mongodb
Post.update({"_id": ObjectId(req.params.id) }, {
$push: {
comment: content,
}
}, function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
});
})
Post Mongoose Schema
Post.js
const mongoose = require("mongoose");
const PostSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
postedAt: {
type: String,
default: new Date().toString()
},
postedBy: {
type: String,
},
warned: {
type: String,
},
comment: [String]
});
module.exports = new mongoose.model("Post", PostSchema);
Everything else works but the array functionality.
I think there are a few mistakes, you didn't await the request and you put "_id" when querying instead of _id.
Another way you could do it too would be using findByIdAndUpdate method.
await Post.findByIdAndUpdate(req.params.id, {
$push: {
comment: content,
},
function(error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
},
});

Updating DB Shema in Express JS with Mongoose library

I have created a Mongo DB schema with Mongoose in Express.js and I am building the REST API. However when I try to update existing records the values that I do not update from the schema automatically become null. I understand why this happens just not sure exactly how it should be coded.
This is the route:
router.patch("/:projectId", async (req, res) => {
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
{
$set: {
title: req.body.title,
project_alias: req.body.project_alias,
description: req.body.description
}
}
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});
also here is the schema:
const ProjectsSchema = mongoose.Schema({
title: {
type: String,
required: true,
unique: true
},
project_alias: {
type: String,
unique: true,
required: true
},
description: String,
allowed_hours: Number,
hours_recorded: {
type: Number,
default: 0
},
date_added: {
type: Date,
default: Date.now
}
});
My problem is that when I want to update just the title:
{
"title" : "Title Updated33"
}
description and alias become null. Should I implement a check?
Just use req.body for the update object like this:
router.patch("/:projectId", async (req, res) => {
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
req.body
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});
Or even better, create a helper function like this so that we can exclude the fields in the body that doesn't exist in the model:
const filterObj = (obj, ...allowedFields) => {
const newObj = {};
Object.keys(obj).forEach(el => {
if (allowedFields.includes(el)) newObj[el] = obj[el];
});
return newObj;
};
router.patch("/:projectId", async (req, res) => {
const filteredBody = filterObj(
req.body,
"title",
"project_alias",
"description",
"allowed_hours",
"hours_recorded"
);
try {
const updatedProject = await Project.updateOne(
{ _id: req.params.projectId },
filteredBody
);
res.json(updatedProject);
} catch (err) {
res.json({ message: err });
}
});

Resources