Button press triggers the last button's press - node.js

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.

Related

How can I recall a GET request with new parameters and have it get the new items with the parameters in MERN stack?

Initially I used a GET request to call every single item in my MongoDB database to my frontend but now I'm trying to implement a filter system where users would narrow down the options presented by the database using filters.
Here is the concept of what i'm trying to do, this is what it looks like:
If someone selects the filter options "SDG 2: Zero Hunger", "1: Discussion Project", and "Demographic", the user will click submit and then only the first card that has all those things will show up on the right of it, not the second one underneath it.
I'm struggling as to how I would send the information as to how to filter the database because I get the error Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.
How could I code it so that once a user clicks submit, it sends an object containing the filter table data, ex. const filterData = {sdg, assignment_type, theme} (where each refers to its respective thing), to the backend where I perform a GET request to the database in which I use the following code to pull the filtered data:
// filtering a project, calling this everytime filter is changed
const filterProject = async (req, res) => {
const {sdg, assignment_type, theme} = req.body
const filteredProjects = await Project.find({sdg: sdg, assignment_type: assignment_type, theme: theme})
res.status(200).json(filteredProjects)
}
Here is the code for my filtering page right now:
// Filterpage.js
import ProjectDetails from '../ProjectDetails'
import Dropdown from './Dropdown'
import { useEffect, useState } from 'react'
const FilterBody = () => {
const [projects, setProjects] = useState(null)
useEffect(() => {
const fetchProjects = async () => {
const response = await fetch('/api/projects') // Change localhost to server name when deploying
const json = await response.json() // contains array of projects
if (response.ok) {
setProjects(json)
}
}
fetchProjects()
}, [])
return (
<div className="filterHome">
<div className="filterTableContainer">
<div className="filterTableTitle">
Filter Table
</div>
<div className="filterSDGDropDown">
<Dropdown />
</div>
</div>
{/* Lists projects */}
<div>
<div className="projects">
{projects && projects.map((project) => (
<ProjectDetails key={project._id} project={project}/>
))}
</div>
</div>
</div>
)
}
export default FilterBody
Here is the actual filter table, I'm calling this class in Filterpage.js
// Dropdown.js - WHERE THE ACTUAL FILTER TABLE IS
import React, { useEffect, useState } from 'react'
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
sdg: 'SDG 1: No Poverty',
assignment_type: 1,
theme: 'Demographic'
};
this.handleSDGChange = this.handleSDGChange.bind(this);
this.handleAssignmentChange = this.handleAssignmentChange.bind(this);
this.handleThemeChange = this.handleThemeChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// Handling all 3 input changes
handleSDGChange(event) {
this.setState({sdg: event.target.value});
}
handleAssignmentChange(event) {
this.setState({assignment_type: event.target.value});
}
handleThemeChange(event) {
this.setState({theme: event.target.value});
}
// Handling all 3 input submissions
handleSubmit(event) {
alert(this.state.sdg + '--- Assignment Type: ' + this.state.assignment_type + '--- Theme: ' + this.state.theme);
event.preventDefault();
// TODO, SEND DATA TO BACKEND TO BE FILTERED
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>SDG:</label>
<select value={this.state.sdg} onChange={this.handleSDGChange}>
<option value="SDG 1: No Poverty">SDG 1: No Poverty</option>
<option value="SDG 2: Zero Hunger">SDG 2: Zero Hunger</option>
<option value="SDG 3: Good Health & Well Being">SDG 3: Good Health & Well Being</option>
</select>
<label>Assignment Type:</label>
<select value={this.state.assignment_type} onChange={this.handleAssignmentChange}>
<option value="1">1: Discussion Project</option>
<option value="2">2: PDF Case study</option>
<option value="3">3: Community Project</option>
</select>
<label>Theme:</label>
<select value={this.state.theme} onChange={this.handleThemeChange}>
<option value="Demographic">Demographic</option>
<option value="Economical">Economical</option>
<option value="Socio-cultural">Socio-cultural</option>
<option value="Technological">Technological</option>
<option value="Ecological">Ecological</option>
<option value="Poltical">Poltical</option>
</select>
<input type="submit" value="Submit" />
</form>
);
}
}
export default Dropdown
Here is my projects.js routes backend code:
const express = require('express')
const {
createProject,
getProject,
getProjects,
deleteProject,
updateProject,
filterProject
} = require('../controllers/projectController')
const router = express.Router()
// GET all workouts
router.get('/', getProjects) // Base route for /api/projects
// FILTER workouts
router.get('/filter', filterProject)
// GET a single workout
router.get('/:id', getProject)
// POST all workouts
router.post('/', createProject)
// DELETE a single workout
router.delete('/:id', deleteProject)
// UPDATE a single workout
router.patch('/:id', updateProject)
module.exports = router
And here is my projectController.js which handles all the requests in the backend (i only included the relevant ones):
const Project = require('../models/projectModel')
const mongoose = require('mongoose')
// get all projects
const getProjects = async (req, res) => {
const projects = await Project.find({}).sort({ createdAt: -1 }) // Specify
// const test = await Project.find({sdg: "SDG 1: No Poverty", assignment_type: 1})
// console.log(test)
res.status(200).json(projects)
}
// filtering a project, calling this everytime filter is changed
const filterProject = async (req, res) => {
const {sdg, assignment_type, theme} = req.body
const filteredProjects = await Project.find({sdg: sdg, assignment_type: assignment_type, theme: theme})
res.status(200).json(filteredProjects)
}
module.exports = {
getProjects,
getProject,
createProject,
deleteProject,
updateProject,
filterProject
}
In addition, is there a way to use react context so that every time the user clicks submit, it will just update the results without refreshing the page?

How to update my array of comments in reactjs?

I have Posts and each post has comments. I created a component and fetched the post's comments. These comments are in array form. I mapped into the array and displayed the comments individually. The challenge I am having now is how to update the individual comments. I am using node.js and mongodb for the backend. I am new to all of these and wondering the way to handle this.
Everything works well when I tested via postman. I can update the comments. To update the comments, I need the comment Id of the comment I want to update. Remember, each comment sits in a post.
In my backend, the path to edit an individual comment is like this:
router.put("/posts/:id/comment/:commentId", async (req, res) =>{
//my codes
}
How to get the comment's id in react is my challenge now and how to fetch the value of each comment so that I can update it. Here are my codes:
export default function Comments() {
const PF = "http://localhost:5000/images/";
const {user} = useContext(Context)
const location = useLocation()
const path = location.pathname.split("/")[2];
const [comments, setComments] = useState([]);// to fetch the comments for individual post
const [updateComment, setUpdateComment] = useState(false);//for change edit mode
const [commentdescription2, setCommentDescription] = useState("")
I fetched the comments for individual posts here:
useEffect(() => {
const getPost = async () =>{
try{
const response = await axios.get("/posts/"+path )
setComments(response.data.comments) //array of comments
}catch(err){
console.log(err)
}
}
getPost()
}, [path])
The function that I tried to write to update the comments. I need the id of the comment to be able to update it to the database:
const handleCommentUpdate = async (id) =>{
const commentId = comments.map((singleId) => singleId._id ==id)
try{
await axios.put("/posts/"+ path + "/comment" + commentId, {
author: user._id,
commentdescription: commentdescription2,
})
window.location.replace("/")
}catch(err){
console.log(err)
}
}
I mapped into the array of comments and displayed them here:
return (
<div className="comment">
<h5 className='users-comment'>Users comments</h5>
{comments.map((singleComment, index)=>{
//console.log(singleComment)
const {author, commentdescription, _id, createdAt} = singleComment
return(
<>
{updateComment == _id ? //toggles comment into edit mode
<div className='comment-div' key={_id} >
<textarea className='comment-wrapper' type='text'
onChange={(e) => setCommentDescription(e.target.value)} value={commentdescription2}>
</textarea>
<button className='comment-btn'onClick={handleCommentUpdate(_id)}>Update Post</button>
</div>
:
<div className='displayed-comments'key={_id}>
<div className="commentPic">
<img className="user-pics" src={PF + singleComment.author.profilePicture} alt="" />
</div>
<div className="comment-info">
<div className="comment-author-div">
<div className='comment-author-date-info'>
<h4 className="comment-author">{singleComment.author.username}</h4>
<p className="comment-date">{new Date(singleComment.createdAt).toDateString()}
</p>
</div>
<div className="comment-edit-delete-div">
<i className="singlePostIcon fas fa-edit" onClick={() => setUpdateComment(_id)} ></i>
<i className="singlePostIcon far fa-trash-alt" ></i>
</div>
</div>
<p className="comment-content">{singleComment.commentdescription}</p>
</div>
</div>
}
</>
)
})}
</div>
)
Error message that I got when I tried using the function that I wrote about is :
Status Code: 404 Not Found
Is there a way out of this? Thanks
You need pass prop onClick a function. So you should update onClick like this.
onClick={() => handleCommentUpdate(_id)}
And you have the id of comment when you call handleCommentUpdate. So you just use it with you logic
const handleCommentUpdate = async (id) => {
try {
await axios.put("/posts/" + path + "/comment" + id, {
author: user._id,
commentdescription: commentdescription2,
});
window.location.replace("/");
} catch (err) {
console.log(err);
}
};

React, update component after async function set

I want to add data and see in below, and also when I start app, I want see added records. But I can see it, when I'm try to writing something in the fields.
The thing is, the function that updates the static list is asynchronous. This function retrieves data from the database, but before assigning it to a variable, the page has been rendered. There is some way to wait for this variable or update information other way than when you try to type it in the fields. This is before the form is approved.
[![enter image description here][1]][1]
class AddAdvertisment extends React.Component <any, any> {
private advertisment;
constructor(props, state:IAdvertisment){
super(props);
this.onButtonClick = this.onButtonClick.bind(this);
this.state = state;
this.advertisment = new Advertisement(props);
}
onButtonClick(){
this.advertisment.add(this.getAmount(), this.state.name, this.state.description, this.state.date);
this.setState(state => ({ showRecords: true }));
}
updateName(evt){
this.setState(state => ({ name: evt.target.value }));
}
....
render() {
return (<React.Fragment>
<div className={styles.form}>
<section className={styles.section}>
<input id="name" onChange={this.updateName.bind(this)} ></input>
<input id="description" onChange={this.updateDescription.bind(this)} ></input>
<input type="date" id="date" onChange={this.updateDate.bind(this)} ></input>
<button className={styles.action_button} onClick={this.onButtonClick.bind(this)}>Add</button>
</section>
</div>
{<ShowAdvertismentList/>}
</React.Fragment>
);
}
class ShowAdvertismentList extends React.Component <any, any>{
render(){
let listItems;
let array = Advertisement.ad
if(array !== undefined){
listItems = array.map((item) =>
<React.Fragment>
<div className={styles.record}>
<p key={item.id+"a"} >Advertisment name is: {item.name}</p>
<p key={item.id+"b"} >Description: {item.description}</p>
<p key={item.id+"c"} >Date: {item.date}</p>
</div>
</React.Fragment>
);
}
return <div className={styles.adv_show}>{listItems}</div>;
class Advertisement extends React.Component {
public static ad:[IAdvertisment];
constructor(props){
super(props);
if(!Advertisement.ad){
this.select_from_db();
}
}
....
select_from_db = async () => {
const res = await fetch('http://localhost:8000/select');
const odp = await res.json();
if(odp !== "brak danych")
odp.forEach(element => {
if(Advertisement.ad){
Advertisement.ad.push(element);
}
else{
Advertisement.ad = [element];
I try to create function and child like:
function Select_from_db(){
const[items, setItems] = useState();
useEffect(() => {
fetch('http://localhost:8000/select')
.then(res => res.json())
.then(data => setItems(data));
}, []);
return <div className={styles.adv_show}>{items && <Child items={items}/>}
</div>;
}
function Child({items}){
return(
<>
{items.map(item => ( ...
))}
</>
And is working good in first moment, but if I want add item to db I must refresh page to see it on a list below.
I use is instead ShowAdvertismentList in render function. Elements be added to db but not showing below. In next click is this same, until refresh page.
And in my opinio better use a list, becouse I musn't want to conect to database every time to download all records.
[1]: https://i.stack.imgur.com/IYSNU.gif
I now recipe. I must change state on componentDidMount in AddAdvertisment class.
async componentDidMount(){
let z = await setTimeout(() => {
this.setState(state => ({ loaded: true}));
}, 1000);
}
render() {
return (<React.Fragment >
(...)
{this.state.loaded ? <ShowAdvertismentList /> : <Loading/>}
</React.Fragment>
);
}

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

I am trying to reset the state to an empty object after every onClick occurs

I am working on a project in React. The idea is that when you search an artist an img render on the pg. Once you click the image a list of collaborating artists is rendered. You can then click a name and see that persons collabpratign artists. Here is my issue: Rather than the state clearing/resetting each time a new artist is clicked, new artists just add on to the original state. Can someone help me figure out how to clear the state so that the state clears and returns a new list of collaborators? Been stuck on this for hours. Here is the code
searchForArtist(query) {
request.get(`https://api.spotify.com/v1/search?q=${query}&type=artist`)
.then((response) => {
const artist = response.body.artists.items[0];
const name = artist.name;
const id = artist.id;
const img_url = artist.images[0].url;
this.setState({
selectedArtist: {
name,
id,
img_url,
},
});
})
.then(() => {
this.getArtistAlbums();
})
.catch((err) => {
console.error(err);
});
}
getArtistCollabs() {
console.log('reached get artist collab function');
const { artistCounts } = this.state;
// console.log(artistCounts);
const artist = Object.keys(artistCounts).map((key) => {
//kate
const i = document.createElement("div");
i.innerHTML = key;
i.addEventListener('click', () => {
this.searchForArtist(key);
})
document.getElementById("collabs").appendChild(i);
});
this.setState({});
}
//kate
renderArtists() {
const artists = this.getArtistCollabs();
}
render() {
const img_url = this.state.selectedArtist.img_url;
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type='text' name='searchInput' className="searchInput" placeholder="Artist" onChange={this.handleChange} />
<input type='submit' className="button" />
</form>
<img className="artist-img" src={this.state.selectedArtist.img_url}
// kate
onClick={this.renderArtists} alt="" />
<div id="collabs">
</div>
</div>
Your problem is right here:
const artist = Object.keys(artistCounts).map((key) => {
//kate
const i = document.createElement("div");
i.innerHTML = key;
i.addEventListener('click', () => {
this.searchForArtist(key);
})
document.getElementById("collabs").appendChild(i);
What you have done here is manually create html elements and insert them into the dom. As soon as this takes place react has no control over these newly created elements. You should only manipulate the DOM like this when its absolutely necessary. Instead you should be making a new component called something like <ArtistCollaborators> and it should take in the artists as props and be what renders the code you have here into the DOM using its own render method.
This will be the React way of doing it, and allows react to be fully control of what you are rendering into the DOM.

Resources