React: Cannot read properties of undefined (reading '0') - node.js

i am getting the error
Grade.jsx:52 Uncaught TypeError: Cannot read properties of undefined (reading '0')
at Grade.jsx:52:1
at Array.map (<anonymous>)
at Grade (Grade.jsx:39:1)
at renderWithHooks (react-dom.development.js:16305:1)
at mountIndeterminateComponent (react-dom.development.js:20074:1)
at beginWork (react-dom.development.js:21587:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
at invokeGuardedCallback (react-dom.development.js:4277:1)
at beginWork$1 (react-dom.development.js:27451:1)
(anonymous) # Grade.jsx:52
Grade # Grade.jsx:39
renderWithHooks # react-dom.development.js:16305
mountIndeterminateComponent # react-dom.development.js:20074
beginWork # react-dom.development.js:21587
callCallback # react-dom.development.js:4164
invokeGuardedCallbackDev # react-dom.development.js:4213
invokeGuardedCallback # react-dom.development.js:4277
beginWork$1 # react-dom.development.js:27451
performUnitOfWork # react-dom.development.js:26557
workLoopSync # react-dom.development.js:26466
renderRootSync # react-dom.development.js:26434
recoverFromConcurrentError # react-dom.development.js:25850
performSyncWorkOnRoot # react-dom.development.js:26096
flushSyncCallbacks # react-dom.development.js:12042
(anonymous) # react-dom.development.js:25651
react-dom.development.js:18687 The above error occurred in the <Grade> component:
at Grade (http://localhost:3000/static/js/bundle.js:6687:56)
at RenderedRoute (http://localhost:3000/static/js/bundle.js:277704:5)
at Outlet (http://localhost:3000/static/js/bundle.js:278056:26)
at div
at StudentProfileLayout (http://localhost:3000/static/js/bundle.js:6946:72)
at RenderedRoute (http://localhost:3000/static/js/bundle.js:277704:5)
at Routes (http://localhost:3000/static/js/bundle.js:278141:5)
at Routing
at App (http://localhost:3000/static/js/bundle.js:349:78)
at Router (http://localhost:3000/static/js/bundle.js:278079:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:276396:5)
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
but if i comment out the grade column value before the Grade comp renders,there will be no error.it will display all the columns with there values after that if i uncomment out it displays the grade column values properly.
but without commenting out the values of the grade column,the error happens
please i need help guys,help.
the server responds with this values
ayne abreham alemayehu
new ObjectId("63efb168503e390f0b7c986a")
new ObjectId("63efb168503e390f0b7c986a")
[
{
gd: {
_id: new ObjectId("63f16c7f03e5a86d748ab2c4"),
batch: 1,
department: 'None',
stream: 'None',
subject: 'b',
class: new ObjectId("63efb168503e390f0b7c986a"),
grades: [Array],
createdAt: 2023-02-19T00:25:35.272Z,
updatedAt: 2023-02-19T00:25:35.272Z,
__v: 0
},
sem: 1
},
{
gd: {
_id: new ObjectId("63f16c8903e5a86d748ab2c9"),
batch: 1,
department: 'None',
stream: 'None',
subject: 'd',
class: new ObjectId("63efb168503e390f0b7c986a"),
grades: [Array],
createdAt: 2023-02-19T00:25:45.106Z,
updatedAt: 2023-02-19T00:25:45.106Z,
__v: 0
},
sem: 1
sem: 1
}
sem: 1
}
]
the route
router.route("/find/grades").post(async (req, res) => {
try {
const { fullName } = req.body;
const user = await User.findOne({ fullName });
console.log(fullName);
const grades = await Grade.find({
department: user.department,
stream: user.stream,
batch: user.batch,
"grades.studentId": user._id,
});
const updatedGrades = await Promise.all(
grades.map(async (gd, i) => {
console.log(gd.class);
const sem = await Class.findById({ _id: gd.class });
return { gd, sem: sem.semester };
})
);
console.log(updatedGrades);
res.json({ updatedGrades });
} catch (err) {
console.log(err);
}
});
Grade.jsx
import { useState, useEffect, useContext } from "react";
import axios from "axios";
import { UserContext } from "../App.jsx";
function Grade() {
const { state, dispatch } = useContext(UserContext);
const [data, setData] = useState([{ gd: {}, sem: 1 }]);
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.post("http://localhost:5000/user/find/grades", {
fullName: state.name,
});
const newData = res.data.updatedGrades.map((item) => ({ gd: item.gd, sem: item.sem }));
console.log(newData);
setData(newData);
} catch (err) {
console.log(err);
}
};
fetchData();
}, []);
console.log(data);
return (
<div className="w-full text-center">
{console.log(data)}
<table className="table-auto lg:table-fixed w-full">
<thead>
<tr className="bg-gray-800 text-white">
<th className="lg:w-52 px-4 py-2">Batch</th>
<th className="px-4 py-2">Department</th>
<th className="px-4 py-2">Stream</th>
<th className="px-4 py-2">Semester</th>
<th className="px-4 py-2">Subject</th>
<th className="px-4 py-2">Grade</th>
</tr>
</thead>
<tbody>
{data && data.length > 0 &&
data.map((item, index) => (
<tr key={index} className="text-gray-700">
<td className="border px-2 py-px">{item.gd.batch}</td>
<td className="text-center border px-0.5 py-px">
{item.gd.department}
</td>
<td className="border px-1.5 py-px">{item.gd.stream}</td>
<td className="border px-2 py-px">{item.sem}</td>
<td className="text-center border px-2 py-px">
{item.gd.subject}
</td>
<td className="text-center border px-2 py-px">
{item.gd?.grades[0]?.grade}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export default Grade;

Can you, please, console log item.gd.grades? I have a feeling that that will be an Array of Arrays, meaning it's not [{ grade: 5 }] but [[{ grade: 5 }]] (note that it is a nested array).
If that would be an array of objects you would see [Object] not [Array]. To see the full log you can do the following: console.log(JSON.stringify(thingie, null, 2)) (where thingie is what you want to log, null is the replacer, 2 - indent, refer to MDN JSON.stringify)

Related

Cannot display api data in ejs template

I can retrieve the api data and console.log it and the for loop is displaying each card div element correctly but I cannot get the details to display for each item.
plant.js controller:
exports.postFindPlant = async (req, res) => {
const plantName = req.body.searchPlant;
const key = process.env.API_KEY;
const urlAPI = `https://trefle.io/api/v1/plants/search?token=${key}&q=${plantName}`;
try {
const response = await fetch(urlAPI);
const data = await response.json();
if (data.meta.total === 0) {
console.log('nada');
} else {
console.log(data.meta.total);
let plants = JSON.stringify(data);
res.render('plants', {
plants: plants,
pageTitle: 'Plants',
path: '/plants',
hasPlants: plants.length > 0
});
}
} catch (err) {
console.log(err);
}
};
plants.ejs:
<% for(var i=0; i < plants.length; i++) { %>
<div class="col s12 m4">
<div class="card">
<div class="card-image waves-effect waves-block waves-light">
<img class="activator" src="<%= plants[i].image_url %>">
</div>
<div class="card-content">
<span class="card-title activator grey-text text-darken-4"><%= plants[i].common_name %><i class="material-icons right">MORE</i></span>
</div>
<div class="card-reveal">
<span class="card-title grey-text text-darken-4"><%= plants[i].common_name %><i class="material-icons right">CLOSE</i></span>
<p><%= plants[i].scientific_name %></p>
<p>Plant Details</p>
</div>
</div>
</div>
<% } %>
In your Node.js code, you get the data from API with:
const data = await response.json();
Usually, some data returned from an API contains much information with keys, so it could be an Object. (I guessed, but confirmed in comments)
And then, if you want to access any info from an Object, you have to refer the value by it's key. For example:
> const person = { name: "Alan", age: 20 }
undefined
> person.name
'Alan'
> person.age
20
But if it's a object which contains an Array, and you want to access the Array's data, you can use Array index after refer the Array with it's key in the Object.
> const person = { name: "Alan", age: 20 }
undefined
> const person2 = { name: "Alice", age: 18 }
undefined
> const people = { type: "Human", data: [person, person2] }
undefined
> people
{
type: 'Human',
data: [ { name: 'Alan', age: 20 }, { name: 'Alice', age: 18 } ]
}
>
For example, how to get Alice in my code above?
> people.data
[ { name: 'Alan', age: 20 }, { name: 'Alice', age: 18 } ]
> people.data[1]
{ name: 'Alice', age: 18 }
> people.data[1].name
'Alice'
>
In your case, you parse the Object to JSON, and passed it into your Ejs code. It's still an Object, so I assume you know how to refer the values you want with correct keys in plants.ejs, and the things you need is only plants.data. Then, just change this line of your plant.js controller below:
let plants = JSON.stringify(data);
To something like:
let plants = JSON.stringify(data).data;
What makes the different? Take a look again to my examples above, this refer to people.data, which is an Array of Objects, instead of an Object which contains an Array of Objects.
So hope you have fully understand what is the problem, and how to solve it.

Get data from Node REST API and populate other controls

After login in React Web app, I am going to show profile detail to user and I am fetching profile detail from NODE REST API. I am not sure how to do that. I think the problem is in the render section, because if I don't use the render section then it is showing me some data.
export class AccountDetails extends Component {
constructor (props) {
super(props)
this.state = {
userDetail:null
}
this.callAPI()
}
async callAPI() {
await fetch("http://localhost:5000/customers?email="+JSON.parse(localStorage.getItem('email'))
.then(res=>res.json())
.then(res=>this.setState({userDetail:res}));
console.log(this.state.userDetail);
}
this is my render section
<tr>
<td className="text-left">First Name : </td>
<td className="text-left">{this.userDetail.firstName}</td>
</tr>
I am getting null in console.log
If I don't use {this.userDetail.firstName} then I am getting
0:
email: "manu#gmail.com"
firstName: "Manpreet"
lastName: "Narang"
occupants: 2
phone: 12345
__proto__: Object
length: 1
__proto__: Array(0)
Since you're using classes you need to make your fetch in componentDidMount like so:
constructor (props) {
super(props)
this.state = {
userDetail: null,
isLoaded: false // Notice this new property and check the render method below
}
}
componentDidMount() {
fetch("http://localhost:5000/customers?email="+JSON.parse(localStorage.getItem('email'))
.then(res => res.json())
.then(res => this.setState({ userDetail: res, isLoaded: true }));
}
Only then will the setState work and rerender your component.
Then, in your render method:
render() {
if(!isLoaded) return <p>Loading...</p>
return (
<tr>
<td className="text-left">First Name : </td>
<td className="text-left">{this.userDetail.firstName}</td>
</tr>
)
}
Reference: https://reactjs.org/docs/react-component.html#componentdidmount

Handlebars: Access has been denied to resolve the property "name" because it is not an "own property" of its parent

I got the issue with handlebars 4.7.3. I already checked the solution from this ask,
Handlebars: Access has been denied to resolve the property "from" because it is not an "own property" of its parent
but it was no solution for my code so I hope someone can help me.
Controller.js
submitPosts: (req, res) => {
// Check the attributs from create.handlebars for success or error message
const newPost = new Post( {
surname: req.body.surname,
name: req.body.name,
biography: req.body.biography,
profilpicture: req.body.profilpicture,
paintings: req.body.paintings,
});
// Safe new posts
newPost.save().then(post => {
console.log(post);
flash('success-message', 'new post!')
res.redirect('/admin/posts');
});
},
postModel.js
const
mongoose = require('mongoose');
const Schema = mongoose.Schema; // get props
const PostSchema = new Schema({
// define props --> required for a post
surname: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
biography: {
type: String,
default: ''
},
profilpicture: {
type: Object,
},
paintings : {
type: Object,
}
});
module.exports = mongoose.model('post', PostSchema);
index.handlebars
{{#each post}}
<tr>
<td>{{ surname }}</td>
<td>{{ name }}</td>
<td><img style="width: 100px; height:100px;" src="{{ profilpicture }}"></td>
<td>{{ biography }}</td>
<td><img style="width: 100px; height:100px;" src="{{ paintings }}"></td>
</tr>
{{/each}}
Already tried every possibility from the other ask on stack overflow, other handlebars version, change router code, ... NOTHING WORKS :(
Just had the same issue that wrecked my nerves.
Tried additional packages etc but in the end a simple command solve this.
Its the Command ".lean()"
Found more here: link to mongoose docs for lean()
my code example:
// show message page Route
router.get("/", (req, res) => {
//read all Message entries from db
Message.find()
//sort them descending by date property
.sort({ date: "desc" })
.lean()
//take these and call the rendered page and pass them as object to the page
.then(messages => {
res.render("messages/show_messages", { messages: messages });
});
});

How to do soft delete with mongodb using nodejs

I'm able to delete data from the view , but at the sametime its getting deleted from mongodb which shouldn't happen.
I tried mongoose-soft-delete plugin to perform soft delete, but it isn't working
//schema
var mongoose= require('mongoose');
let softDelete = require('mongoosejs-soft-delete');
var Schema=mongoose.Schema;
var newblogSchema=new Schema({
user_id:Number,
title:String,
description:String,
summary:String,
hashtag:String
})
var newblogs=mongoose.model('NewBlog',newblogSchema);
newblogSchema.plugin(softDelete);
module.exports=newblogs;
//html template
<table>
<tr>
<th>Title</th>
<th>Description</th>
<th>Summary</th>
<th>HashTags</th>
</tr>
<tr *ngFor="let blog of blogs;">
<td >{{blog.title}}</td>
<td [innerHtml]="blog.description| safeHtml">{{blog.description}}</td>
<td>{{blog.summary}}</td>
<td>{{blog.hashtag}}</td>
<td> <a routerLink="/blog"><button type="button"
(click)="editblog(blog._id,blog.title,blog.description,blog.summary,blog.hashtag)">
Edit</button></a>
<td><button type="button" (click)="deleteblog(blog._id)">Delete</button>
</tr>
</table>
//ts file
deleteblog(blogid) {
var result = confirm('Want to delete?');
if (result === true) {
this.blogservice.deleteblog(blogid).subscribe(response => {this.blogs = response; });
}
//service
deleteblog(blogid):Observable<any>{
return Observable.create(observer=>{
this.http.post('http://localhost:4000/api/deleteblog', {_id: blogid}, {headers: new HttpHeaders({'Content-Type':'application/json'})}
)
.subscribe((response:Response)=>{
observer.next(response);
observer.complete();
});
});
}
//api.js
router.post('/deleteblog',(req,res)=>{
var body=req.body;
newblog.findByIdAndRemove({_id:body._id},(error,newblog)=>{if(error){
console.log(error);
}
else{
return res.json({message:'deleted',data:newblog});
}
});
});
Now the data is getting deleted from view as well as mongodb.
Expected result is to delete data only from the view and not from mongodb
we can implement soft delete with plugin, middleware and $isDeleted document method
soft delete plugin code:
import mongoose from 'mongoose';
export type TWithSoftDeleted = {
isDeleted: boolean;
deletedAt: Date | null;
}
type TDocument = TWithSoftDeleted & mongoose.Document;
const softDeletePlugin = (schema: mongoose.Schema) => {
schema.add({
isDeleted: {
type: Boolean,
required: true,
default: false,
},
deletedAt: {
type: Date,
default: null,
},
});
const typesFindQueryMiddleware = [
'count',
'find',
'findOne',
'findOneAndDelete',
'findOneAndRemove',
'findOneAndUpdate',
'update',
'updateOne',
'updateMany',
];
const setDocumentIsDeleted = async (doc: TDocument) => {
doc.isDeleted = true;
doc.deletedAt = new Date();
doc.$isDeleted(true);
await doc.save();
};
const excludeInFindQueriesIsDeleted = async function (
this: mongoose.Query<TDocument>,
next: mongoose.HookNextFunction
) {
this.where({ isDeleted: false });
next();
};
const excludeInDeletedInAggregateMiddleware = async function (
this: mongoose.Aggregate<any>,
next: mongoose.HookNextFunction
) {
this.pipeline().unshift({ $match: { isDeleted: false } });
next();
};
schema.pre('remove', async function (
this: TDocument,
next: mongoose.HookNextFunction
) {
await setDocumentIsDeleted(this);
next();
});
typesFindQueryMiddleware.forEach((type) => {
schema.pre(type, excludeInFindQueriesIsDeleted);
});
schema.pre('aggregate', excludeInDeletedInAggregateMiddleware);
};
export {
softDeletePlugin,
};
you can use as global for all schemas
mongoose.plugin(softDeletePlugin);
or for concrete schema
For Soft delete, you should maintain an active flag column that should only contain values as 0 and 1.
This way, you could analyse whether a record is deleted or not.
While displaying, add another clause for displaying only the records that have flag value 1. And while deleting, just update that flag's value to 0.
This would do the job.
For Example, here user 2 is deleted. with activeFlag as 0.
ID memberID userStatus groupCode activeFlag
1 user1 1 4455 1
2 user2 1 4220 0
3 user3 2 4220 1
Try to use https://www.npmjs.com/package/soft-delete-mongoose-plugin
A simple and friendly soft delete plugin for mongoose.

how to associate data schema in mongoose

I'm trying to associate 2 schema on nodejs/mongoose app.
my first model:
var mongoose = require("mongoose");
var projectSchema = new Schema({
pname: String,
pnumber: String,
plocation: String,
pclient: String,
clientabn: String,
contname: String,
contnumber: String,
mobile: String,
address: String,
city: String,
country: String,
boqs: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Boq",
}]
});
module.exports = mongoose.model("Project", projectSchema);
second model:
var mongoose = require("mongoose");
var boqSchema = new Schema({
boqDesc: String,
boqUnit: String,
boqQty: String,
boqRate: String,
boqPrice: String,
});
module.exports = mongoose.model("Boq", boqSchema);
And this is my post rout:
app.post("/myprojects/:id/boq", function(req, res) {
Project.findById(req.params.id, function(err, project) {
if (err) {
console.log(err);
} else {
Boq.create(req.body.boq, function(err, boq) {
if (err) {
console.log(err);
} else {
project.boqs.push(boq);
project.save();
res.redirect("/myprojects/" + project._id + "/boq");
console.log(boq);
}
});
}
});
});
When is posting, only an id of boq will be saved on database not any data from boq's form. anyone knows what is wrong with this code?
output of console.log:
console.log output
console.log(project)
Below is the post form:
<form action="/myprojects/<%= project._id %>/boq" method="POST">
<table class="table" id="toptab">
<tbody>
<tr>
<td class="td6"><b>No.</b></td>
<td class="td7"><b>Item Description/Scope of Work</b></td>
<td class="td8"><b>Unit</b></td>
<td class="td9"><b>Quantity</b></td>
<td class="td10"><b>Rate</b></td>
<td class="td11"><b>Price</b></td>
</tr>
<tr>
<td class="td6"></td>
<td class="td7" contenteditable="true" name="boq[boqDesc]"></td>
<td class="td8" contenteditable="true" name="boq[boqUnit]"></td>
<td class="td9" contenteditable="true" name="boq[boqQty]"></td>
<td class="td10" contenteditable="true" name="boq[boqRate]">$</td>
<td class="td11" contenteditable="true" name="boq[boqPrice]"></td>
</tr>
</tbody>
</table>
<button type="submit" name="submit" class="btn btn-primary">Save</button>
</form>
you're calling Boq.create(undefined, callback);
this creates a boq document in db with no fields (except the _id).
it passes validation because all boq's fields are not required.
go to your client, and make sure you're adding boq data to http request's body correctly.
add <input> elements in between <td></td> tags, each named after one of the fields in boq's schema. for instance <td><input type="text" name="boqDesc"></td>

Resources