In React, how do I display the results from an API get request? - node.js

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}.

Related

Button press triggers the last button's press

I'm new to react an am trying to create an app to use in my portfolio. Essentially the program is a menu that has access to different menus(json files: texas_pick.js, breakfast.js...), the program is meant to display the menu options in form of buttons, the buttons' details are retrieved from their respective json file. The problem that I am facing is that when making a click on a menu option the data of the last menu item is retrieved. I programmed the backend to only push the item's name and price to the database, and the frontend, to retrieve this data and display it on a table. The data retrieved is only the last button's and not any others. I believe the problem to possibly be within my button code. Any help/tips/recommendations you could give are greatly appreciated.
I clicked every menu item and only the data from the last one was retrieved
import React from 'react'
import {useEffect,useState} from 'react'
import axios from 'axios'
import Texas_Pick from '../../json_files/texas_pick.json'
import './Mid_Container.css'
function Mid_Container() {
const [items, setItems] = useState(Texas_Pick);
const [order, setOrder] = useState({
item: '',
cost: ''
})
const createOrder = () => {
axios
.post("http://localhost:5000/orders", order)
.then(res => {window.location.reload(false)})
.catch(err => console.error(err));
}
const item1 = items[0];
const item2 = items[1];
const item3 = items[2];
const item4 = items[3];
const item5 = items[4];
const item6 = items[5];
return (
<div className="Mid_Container">
<button
style={{backgroundImage: `url(${item1.image})`}}
value={order.item=item1.item,order.cost=item1.price}
onClick={createOrder}
>
<p id="pPrice">${item1.price}</p>
<p id="pItem" >{item1.item}</p>
</button>
<button
style={{backgroundImage: `url(${item2.image})`}}
value={order.item=item2.item,order.cost=item2.price}
onClick={createOrder}
>
<p id="pPrice">${item2.price}</p>
<p id="pItem" >{item2.item}</p>
</button>
<button
style={{backgroundImage: `url(${item3.image})`}}
value={order.item=item3.item,order.cost=item3.price}
onClick={createOrder}
>
<p id="pPrice">${item3.price}</p>
<p id="pItem" >{item3.item}</p>
</button>
<button
style={{backgroundImage: `url(${item4.image})`}}
value={order.item=item4.item,order.cost=item4.price}
onClick={createOrder}
>
<p id="pPrice">${item4.price}</p>
<p id="pItem" >{item4.item}</p>
</button>
</div>
)
}
export default Mid_Container
I think that you should have this approach:
function SomeComponent() {
// Mocking your datas
const [items, setItems] = React.useState([
{
price: "1",
item: "i am the first",
image: "image1.png",
},
{
price: "7",
item: "I am the second",
image: "image2.png",
},
{
price: "3",
item: "i am the third",
image: "image3.png",
},
]);
const [order, setOrder] = React.useState();
const [myResponse, setMyResponse] = React.useState();
const createOrder = (clickedItem) => {
setOrder(clickedItem);
console.log(clickedItem);
// axios
// .post("http://somewhere", clickedItem)
// .then((res) => {
// setMyResponse(res); // or setMyResponse(res.json());
// })
// .catch((err) => console.error(err));
};
console.log('Log selected order in render loop ==> ', order);
console.log('Log response in render loop ==> ', myResponse);
return (
<div>
<div className="Mid_Container">
{items.length && items.map((currItem, index) => {
return (
<button
key={index}
style={{ backgroundImage: `url(${currItem.image})` }}
onClick={() => createOrder(currItem)}
>
<p id="pPrice">${currItem.price}</p>
<p id="pItem">{currItem.item}</p>
</button>
);
})}
</div>
</div>
);
}
Mapping on your items with map function, and pass the current item to your onClickEvent.
I also think you don't need a value attribute on your buttons. It's also not the place to do operations like you do :)
You also don't have to reload the page in the "then" of your promise. React is made to do SPA (single page application), so in the "then", you can put some code like "setResult(myResponse)" to store in you component the new data you got from your API.

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.

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

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.

Can't Edit and Update properties with form Reactjs and MongoDB

So I'm using Nodejs, MongoDB and Reactjs
and I'm trying to Edit properties of projects.
I have multiple projects and when I want to edit properties of one I can't do it. We can access to properties inside inputs, we can see Title and Type but can't even delete, write, he access to properties by its ID but then I can't change it, I guess I have multiple problems here than.
I'll write here my server code, and my Edit/Update project page and a gif with an example when I say that I can't even change anything on inputs.
My server code:
//Render Edit Project Page byId
app.get('/dashboard/project/:id/edit', function(req, res){
let id = req.params.id;
Project.findById(id).exec((err, project) => {
if (err) {
console.log(err);
}
res.json(project);
});
}
//Update Projects Properties byId
app.put('/dashboard/project/:id/edit', function(req, res){
var id = req.params.id;
var project = {
title: req.body.title,
typeOfProduction: req.body.typeOfProduction
};
Project.findByIdAndUpdate(id, project, {new: true},
function(err){
if(err){
console.log(err);
}
res.json(project);
})
};
My React Component Edit Project Page
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import './EditProject.css';
class EditProject extends Component {
constructor(props){
super(props);
this.state = {
//project: {}
title: '',
typeOfProduction: ''
};
}
inputChangedHandler = (event) => {
const updatedProject = event.target.value;
}
componentDidMount() {
// console.log("PROPS " + JSON.stringify(this.props));
const { match: { params } } = this.props;
fetch(`/dashboard/project/${params.id}/edit`)
.then(response => { return response.json()
}).then(project => {
console.log(JSON.stringify(project));
this.setState({
//project: project
title: project.title,
typeOfProduction: project.typeOfProduction
})
})
}
render() {
return (
<div className="EditProject"> EDIT
<form method="POST" action="/dashboard/project/${params.id}/edit?_method=PUT">
<div className="form-group container">
<label className="form--title">Title</label>
<input type="text" className="form-control " value={this.state.title} name="title" ref="title" onChange={(event)=>this.inputChangedHandler(event)}/>
</div>
<div className="form-group container">
<label className="form--title">Type of Production</label>
<input type="text" className="form-control " value={this.state.typeOfProduction} name="typeOfProduction" ref="typeOfProduction" onChange={(event)=>this.inputChangedHandler(event)}/>
</div>
<div className="form-group container button">
<button type="submit" className="btn btn-default" value="Submit" onClcik={() => onsubmit(form)}>Update</button>
</div>
</form>
</div>
);
}
}
export default EditProject;
Erros that I have:
1- DeprecationWarning: collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead.
2- Inputs can't change
3- When click "Update" button:
I think your update override the entire object because you forgot the $set operator. This is the operator to change only the atributtes of an object and not the entire object replacing!
Example:
Model.update(query, { $set: { name: 'jason bourne' }}, options, callback)
First of all, concerning the deprecation warning, you need to change the method findAndModify (As I do not see it here, I guess you're using it elsewhere, or maybe one of the methods you use is calling it) by one of the suggested methods and change your code accordingly.
Then, you need to learn about React and controlled components : https://reactjs.org/docs/forms.html
You need to set the component's state in your onChange handler, such as :
this.setState({
title: event.target.value // or typeOfProduction, depending on wich element fired the event
});
This is called a controlled component in React.
Concerning the response body you get when clicking on Update button, this is actually what you asked for :
res.json(project);
returns the project variable as a JSON file, which is displayed on your screenshot.
See this question for more information about it : Proper way to return JSON using node or Express
Try replace "value" in input tag with "placeholder"

How search value inside an object in Redis?

I'm newbie to Redis and Nodejs, I've watched this tutorial and i'm trying to search users by name,
Here is the object, returned from redis when i pass the id:
{
first_name: '‪john',
last_name: 'doe',
email: 'john#gmail.com',
phone: '543313305',
id: 'user001'
}
Here is the function of the search :
app.post('/user/search',function (req,res,next) {
let id = req.body.id;
client.hgetall(id ,function(err,obj){
if(!obj){
res.render('searchusers',{
error:"user doesn't exist",
});
} else {
obj.id = id
console.log(obj);
res.render('details',{
user:obj, });
}
});
});
I've tried to replace the search by id to search by first_name by doing this:
First I've changed the field name to "first_name" instead of "id"
<h1>Search Users</h1>
{{#if error}} <span>{{error}}</span>{{/if}}
<form class="form-inline" method="POST" action="/user/search">
<div class="form-group">
<input type="text" name="first_name" placeholder="Search" class="form-
control">
</div>
<input type="submit" class="btn btn-primary" value="Search">
And than I've changed it in the app.js ;
app.post('/user/search',function (req,res,next) {
let first_name = req.body.first_name;
client.hgetall(first_name ,function(err,obj){
if(!obj){
res.render('searchusers',{
error:"user doesn't exist",
});
} else {
obj.first_name = first_name
console.log(obj);
res.render('details',{
user:obj, });
}
});
});
The hgetall method that you are using in the search function of the method looks up a hash by the key, which in this case is the user id and returns all of the fields of the hash from Redis.
There is no function to search over the fields of the hash. If you need to be able to map from a first name to a user, you will need to manually build a secondary index using a data structure such as sets, that allows you to go from first name to the users that have that first name.

Resources