Selecting first element from the dropdown [ select tag ] cause the backend to crash but selecting other works just fine - node.js

I'm building a simple app with node.js and reactjs but the problem arises when I want to add book especially while selecting dropdown value.
In the select tag when I select the first element it crashes the backend but when I select the second or below it they work just fine. What could be the reason behind it ? Dropdown menu has been filled correctly. My code is :
import React, { Component } from 'react'
import axios from 'axios'
export default class AddBook extends Component {
state = {
name : '',
author : '',
publishers : '',
pages :'',
genres : [],
genre : '',
addedMessage : null
}
async componentDidMount() {
const genres = await axios.get('http://localhost:5000/api/genres')
this.setState({ genres : genres.data})
}
onInputChange = (e) => {
this.setState({[e.target.name] : e.target.value }) //looks for name
}
onFormSubmit = (e) => {
e.preventDefault();
const book = {
name : this.state.name,
author : this.state.author,
publishers : this.state.publishers,
pages : this.state.pages,
genres : this.state.genre
}
axios.post('http://localhost:5000/api/books', book)
.then(res => console.log(res))
.catch(err => console.log("error occured while posting data ", err))
console.log(book)
//alert("book added successfully !!!")
this.setState({addedMessage : 'Book added successfully'})
window.location = "/books"
}
render() {
const { name, author, publishers, pages, genres, addedMessage } = this.state;
return (
<div className="container">
<h2>ADD BOOK </h2>
{ addedMessage && <h2 style={{textAlign : 'center'}} className="alert alert-info">{addedMessage}</h2> }
<form onSubmit = { this.onFormSubmit }>
<div className="form-group">
<label htmlFor="name">BookName:</label>
<input type="text" required className="form-control" id="name"
placeholder="Enter Book name" name="name"
onChange={this.onInputChange}
value={name}
/>
</div>
<div className="form-group">
<label htmlFor="author">Author:</label>
<input type="text" required={ true } className="form-control"
id="author" placeholder="Enter Author" name="author"
onChange={this.onInputChange}
value={author}
/>
</div>
<div className="form-group">
<label htmlFor="publishers">Publishers:</label>
<input type="text" required className="form-control"
id="publishers" placeholder="Enter Publishers Name"
name="publishers"
onChange={this.onInputChange}
value={publishers}
/>
</div>
<div className="form-group">
<label htmlFor="pages">Pages:</label>
<input type="number" required className="form-control"
id="pages" placeholder="Enter No of Page" name="pages"
onChange={this.onInputChange}
value={pages}
/>
</div>
<div className="form-group">
<label htmlFor="genre">Category:</label>
<select type="select" className="form-control"
id="genre"
name="genre"
onChange={this.onInputChange}
>
{/* <option value="selectCategory" onChange={this.onInputChange}>Select</option> */}
{
genres.map( genre => (
<option key={genre._id} onChange={this.onInputChange} value={genre.name}>
{genre.name }</option>
))
}
</select>
</div>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</div>
)
}
}
My backend is up and running and has successfully fetched the genres[ categories ] , but I am not able
to figure out why selecting the first doesn't work but others just work fine. How should I solve it ?
It shows the following error for this case.
Error: Book validation failed: genres: Path `genres` is required.
at ValidationError.inspect
(E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\error\validation.js:48:26)
at formatValue (internal/util/inspect.js:718:31)
at inspect (internal/util/inspect.js:287:10)
at afterInspector (internal/errors.js:682:14) {
errors: {
genres: ValidatorError: Path `genres` is required.
at validate (E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\schematype.js:1178:13)
at E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\schematype.js:1161:7
at Array.forEach (<anonymous>)
at SchemaString.SchemaType.doValidate
(E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\schematype.js:1106:14)
at E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\document.js:2387:18
at processTicksAndRejections (internal/process/task_queues.js:79:11) {
properties: [Object],
kind: 'required',
path: 'genres',
value: '',
reason: undefined,
[Symbol(mongoose:validatorError)]: true
}
},
_message: 'Book validation failed'
}
[nodemon] app crashed - waiting for file changes before starting...
And code for my book schema is as follows
const mongoose = require('mongoose')
//schema for genres
const bookSchema = mongoose.Schema({
name : {
type : String,
required : true,
trim : true,
unique : true //title is made unique.
},
author : {
type : String,
required : true,
trim : true
},
publishers : {
type : String,
trim : true,
},
pages : {
type : Number,
required : true,
trim : true
},
genres : {
type : String,
required : true,
trim : true
},
create_date : {
type : Date,
default : Date.now
}
})
//It will create books collection in your database and documents
//inside that collection will have fields from bookSchema when you save first document.
const Book = module.exports = mongoose.model('Book', bookSchema)
//get the books
module.exports.getBooks = (callback, limit) => {
// Book.find(callback)
Book.find(callback).limit(limit)
}
//get single book
module.exports.getBookById = (id,callback) => {
// Book.find(callback)
Book.findById(id,callback);
}
module.exports.addBook = (book,callback) => {
Book.create(book, callback);
}
module.exports.updateBook = (id,book,options, callback) => {
const query = {
_id : id
}
const updatedBook = {
name : book.name,
author : book.author,
publishers : book.publishers,
pages : book.pages,
genres : book.genres
}
Book.findByIdAndUpdate(query, updatedBook, {} , callback);
}
module.exports.deleteBook = (id,callback) => {
const query = {
_id : id
}
Book.findByIdAndDelete(query, callback);
}
I checked some of the similar answers in the stackoverflow but couldn't figure out why I'm getting that
error.

Here's the problem with your code :
The first option of the dropdown is chosen by default, and thus "selecting" the first option doesn't trigger the Category dropdown's onChange function (because the selected value doesn't really change).
Since the default value of this.state.genre is an empty string, the genres property of the book you send to the backend also has genres as an empty string.
Finally, Mongoose does not accept an empty string for a required string field, which gives you the Path 'genres' is required error.
Try switching to another option and back to the first. It will work then because the onChange function will then get called and this.state.genre will be set properly.
There are a few ways to fix this:
Add a placeholder option to the dropdown - something like "Select an option...". Then the user will be forced to select a genre from the dropdown and onChange would be called.
Or, in componentDidMount, set this.state.genre to the first genre of the list of genres you fetch.

Related

MeanStack: conflict on editing feature'_id' would modify the immutable field '_id'

I've seen solutions in this post and in others, but I did follow the instructions and I'm still getting the error. I understand all the logic, id's can't be replaced, they are immutable, but in my mean stack app, the error persists. The code:
Node route
router.put("/:id" ,(req, res, next) => {
const note = new Note({
_id:req.body.id,
project: req.body.project,
note: req.body.note,
date:req.body.date,
});
Note.updateOne({ _id: req.params.id }, note).then(result => {
console.log(result)
res.status(200).json({ message: "Update successful!" });
});
});
Front End edit component:
ngOnInit(): void {
this.notesForm=this.fb.group({
note:['', Validators.required],
project:['', Validators.required],
date:[null,Validators.required]
})
this.route.paramMap.subscribe((paraMap:ParamMap)=>{
if(paraMap.has('noteId')){
this.mode='edit';
this.noteId= paraMap.get('noteId');
this.note=this.notePadService.getNote(this.noteId)
.subscribe(noteData=>{
this.note = {id: noteData._id, note: noteData.note, date: noteData.date, project: noteData.project};
})
}else{
this.mode='create';
this.noteId=null;
}
})
this.getProjects();
}
onSubmit(){
if(this.mode==='create'){
this.notePadService.submitNotes(this.notesForm.value)
console.log(this.notesForm.value);
this.router.navigate(['/notes-listing'])
}else{
this.notePadService.updateNote(
this.noteId,this.notesForm.value
)
}
}
Service:
getNote(id:string){
return this.http.get<any>('http://localhost:3000/api/notes/'+id)
}
updateNote(id:string,note:Notes){
this.http.put('http://localhost:3000/api/notes/'+id,note)
.subscribe(response=>console.log(response))
}
Also I cant pre-populate the reactive form with the values to edit:
<label>Select a Project</label>
<select (change)="test($event.target.value)" formControlName="project"
class="form-control">
<option
*ngFor="let p of projects"
[value]="p.name">{{ p.name }}</option>
</select>
<!------->
<label for="notes">Note</label>
<textarea class="form-control" formControlName="note" type="text" rows="4"></textarea>
<div class="input-group">
<mat-form-field appearance="fill">
<mat-label>Choose a date</mat-label>
<input formControlName="date" matInput [matDatepicker]="picker">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
<button mat-raised-button color="warn">Submit</button>
</form>
When I click the edit button (in another component), I get the correct URL but no values, and onsubmit, the backend crashes:
http://localhost:4200/edit/601ec21882fa6af20b454a8d
Can someone help me out? I'm very confused and I cant understand the error since I've done everything the other posts suggest...
when you call updateOne you already pass as the first argument the id that should be updated. As second argument you pass note that should contain only the properties that will be updated, but not _id itself:
const note = {
project: req.body.project,
note: req.body.note,
date:req.body.date,
};

Filtering node.js (express, mongoose) app

I am trying to a node.js application (express, mongoose) and I want to give the option to the user to filter the data they receive through checkboxes. In the front end I store the user's choice in cookies (I do not know know if there is a better way) but I failed to filter the data in my database. The html code is that:
A modal that gives the user the option to check for specific data
<div id="modal">
<div id="modalContent">
<h3>Filters</h3><svg xmlns='http://www.w3.org/2000/svg' id="closeBtn" class='ionicon' viewBox='0 0 512 512'><title>Close</title><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32' d='M368 368L144 144M368 144L144 368'/></svg>
<div>
<form method="GET">
<div>
<input type="checkbox" class="save-cb-state" name="Vasilopoulos" value="Vasilopoulos">Vasilopoulos
</div>
<div>
<input type="checkbox" class="save-cb-state" name="Masoutis" value="Masoutis">Masoutis
</div>
<div>
<input type="checkbox" class="save-cb-state" name="Web Only" value="Web Only">Web Only
</div>
<div>
<input type="checkbox" class="save-cb-state" name="In Store" value="In Store">In Store
</div>
<button type="submit">Αναζήτηση</button>
</form>
</div>
</div>
</div>
My model is that:
The checkboxes are filtering for storeName and/or offerType (The problem is when the user wants to filter both data)
const mongoose = require("mongoose");
const offersSchema = new mongoose.Schema({
imgLink: String,
title: String,
startPrice: Number,
discountPrice: Number,
pageLink: String,
storeName: String,
offerType: String,
discountPercentage: {
type: String,
trim: true
}
});
module.exports = mongoose.model("Offers", offersSchema);
The first two checkboxes are referring to StoreName attribute of my model and the last two are referring to offerType
The code in my controller is this:
async function getOffers(req) {
const cookiesArray = Object.values(req.cookies);
const page = parseInt(req.query.page || 1);
const sort = req.query.sort || "discountPrice";
const itemsPerPage = 10;
let products;
//If cookiesArray length is greater than 0 means that the user has check at least one checkbox
if(cookiesArray.length !== 0) {
products = await Offers.find({ offerType: { $in: cookiesArray}, storeName: { $in: cookiesArray } })
.skip((page - 1) * itemsPerPage)
.limit(itemsPerPage)
.sort(sort);
} else {
products = await Offers.find()
.skip((page - 1) * itemsPerPage)
.limit(itemsPerPage)
.sort(sort);
}
return {
category: "offers",
products,
sort,
currentPage: page,
hasNextPage: itemsPerPage * page < totalProducts,
hasPreviousPage: page > 1,
nextPage: page + 1,
previousPage: page - 1,
lastPage: Math.ceil(totalProducts / itemsPerPage)
}
}
getOffers is a helper function that my controller uses to fetch data. I tried to use the in operator but if the user check one or two values from the first two checkboxes and one or two values from the last two check boxes the in operator fails.

I get a SyntaxError when I type 'npm run serve'. The error is: Unexpected token, expected "," (45:2). What am I doing wrong?

I get a SyntaxError when I type: npm run serve
The error is: Unexpected token, expected "," (45:2)
What am I doing wrong?
I am working with Pusher and Vue.js in Visual-Studio-Code.
This is a Tutorial from a Website : https://www.sitepoint.com/pusher-vue-real-time-chat-app/
Below is my Code. Please help me guys!!
<template>
<div class="login-form">
<h5 class="text-center">Chat Login</h5>
<hr>
<b-form #submit.prevent="onSubmit">
<b-alert variant="danger" :show="hasError">{{ error }} </b-alert>
<b-form-group id="userInputGroup"
label="User Name"
label-for="userInput">
<b-form-input id="userInput"
type="text"
placeholder="Enter user name"
v-model="userId"
autocomplete="off"
:disabled="loading"
required>
</b-form-input>
</b-form-group>
<b-button type="submit"
variant="primary"
class="ld-ext-right"
v-bind:class="{ running: loading }"
:disabled="isValid">
Login <div class="ld ld-ring ld-spin"></div>
</b-button>
</b-form>
</div>
</template>
// something after .env
<script>
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
name: 'login-form',
data() {
return {
userId: '',
}
}
methods: {
...mapActions ([
'login'
]),
async onSubmit() {
const result = await this.login(this.userId);
if(result) {
this.$router.push('chat');
}
}
}
computed : {
isValid: function(){
const result = this.userId.length < 3;
return result ? result : this.loading
},
...mapState([
'loading'
'error'
]),
...mapGetters([
'hasError'
])
}
}
</script>
You're missing some commas in your object. Javascript objects need commas between keys.
let object = {
key: "value",
otherKey: "otherValue"
}
Note the , after "value".
The object you're exporting doesn't have a comma after the data or methods keys, even though other keys follow.
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Basics

In React, how do I display the results from an API get request?

I've created API's for a standard transaction application (using mongoDB for DB), I have API's for users, products and orders. I managed to use and display the information stored about users and products, however, when I try to follow the same code for displaying my orders (which shares objects from products and users, in mongoDB collections) I get stuck. I know this has something to do with how I've named (const { _id, username, useremail, productname, productsellprice } = order;) as the _id works.
Other details: using= axios for http requests; context api with hooks (useContext, useReducer)
Im new to all this so forgive me if I've left out crucial information.
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import OrderContext from '../../context/order/orderContext';
const OrderItem = ({ order }) => {
const orderContext = useContext(OrderContext);
const { deleteOrder, setCurrent, clearCurrent } = orderContext;
const { _id, username, useremail, productname, productsellprice } = order;
const onDelete = () => {
deleteOrder(_id);
clearCurrent();
};
return (
<div className='card bg-light'>
<h3 className='text-primary text-left'>
{_id}{' '}
<span
style={{ float: 'right' }}
className={
'badge ' +
(productsellprice === 'professional' ? 'badge-success' : 'badge-primary')
}
>
{'£ ' + productsellprice}
</span>
</h3>
<ul className='list'>
{useremail && (
<li>
<i className='fas fa-marker' /> {useremail}
</li>
)}
{productname && (
<li>
<i className='fas fa-marker' /> {productname}
</li>
)}
</ul>
<p>
<button
className='btn btn-dark btn-sm'
onClick={() => setCurrent(order)}
>Edit
</button>
<button className='btn btn-danger btn-sm' onClick={onDelete}>
Delete
</button>
</p>
</div >
);
};
OrderItem.propTypes = {
order: PropTypes.object.isRequired
};
export default OrderItem;
I want to display the order data in a simple ui card. Currently it only displays the order_id
When you print order in console, you get this,
Object product: {_id: "5d1a62c40d16f11d94009a7a", name: "Lead18", sellprice: 104} saledate: "2019-07-03T19:08:05.630Z" user: email: "irl1984#yahoo.co.uk" name: "Kevin Kane" _id: "5d1cd8fbc02bc928d83ea109" proto: Object _id: "5d1cfd156cd4673840981c5d" proto: Object OrderItem.js:6 Object product: {_id: "5d1a62c40d16f11d94009a7a", name: "Lead18", sellprice: 104} saledate: "2019-07-03T18:43:14.409Z" user: {_id: "5d1cd8fbc02bc928d83ea109", name: "Kevin Kane", email: "irl1984#yahoo.co.uk"} _id: "5d1cf74207fa3337cceb5e1e" proto: Object
It means you are getting object inside object.
So instead of this,
const { _id, username, useremail, productname, productsellprice } = order;
You should use Destructuring nested objects,
const { product:{_id: product_id}, user:{name: username}, user:{email: useremail}, product:{name: productname}, product:{sellprice: productsellprice} } = order;
Here I am not sure which _id you want to show, if you want _id from user object then use this use:{_id}.

html form input name for mongoose

MongoDB
{
"_id" : ObjectId("5bfac9c5c44526e73f960e89"),
"brand" : "Under Armour",
"amazon" : {
"order" : "666-666-666",
"id" : "B072LNJPS1"
}
...
}
HTML
<form....>
<input class="form-control" type="text" name="brand">
<input class="form-control" type="text" name="amazon.id">
<input class="form-control" type="text" name="amazon.order">
...
</form>
NodeJs
const myPost = request.body
const query = { _id: request.body._id }
const options = { upsert: true, new: true }
await model.findOneAndUpdate(query, myPost, options);
I have this error message because of the '.' in the field name
errmsg: 'Updating the path 'amazon.id' would create a conflict at 'amazon''
You have tips for the field name to match the model structure
This may not be a complete answer, but you need to replace "amazon.id" with "amazon[id]" to match the nested structure.
Reference: Colt Steele's "The Web Developer Bootcamp"

Resources