Save a specific value from an array of object and store them individually into another state to get the sum ReactJS - node.js

In addition to this question
I am trying to map individually a state to another state to store the amountToPay object to get the sum of it. The problem is every time it renders the onChange function. It stores every state as object as you can see here: .
What I want to happen is to only get [434] instead of ['','4','43','434']
So I can .reduce the array to get the sum.
My method on storing the array object to another state is this
const [amountToPay, setAmountToPay] = useState("")
console.log("AMOUNT TO PAY", amountToPay)
useEffect(() => {
serviceItem.map((item) => (
setAmountToPay([...amountToPay, item.amountToPay])
))
}, [serviceItem])
useEffect(() => {
serviceItem.map((item) => (
setAmountToPay([...amountToPay, item.amountToPay])
))
}, [serviceItem])
You can check the whole code here CodeSandbox code.Any help is appreciated :)

There are several things I suggest you to do:
Add some id property to your serviceItem. You can use UUID, nanoid, or even Date.now()
Remove const [amountToPay, setAmountToPay] = useState([]);
Use values directly from serviceItem collection. In order to do this you need to create onChange handler, it will be something like this
const handleChange = (id) => (nextAmount) => {
setServiceList(prevValue => {
return prevValue.map(item => item.id === id ? { ...item, amount: nextAmount } : item)
})
}
And amount to pay can be easily got from serviceItem collection, without effects or other states
const procedurePriceTotal = serviceItem.reduce(
(acc, item) => (acc = acc + item.amount),
0
);

this is happening because you are setting serviceItem on onChange method
and use passed serviceItem as deps array to useeffect in which you are setting amountToPay.
so on every change it's appending in array
Rather then setting amount in useEffect, make a method and call on remove/add button so it will only call after user is finished typing. you can also place a button 'Save' or 'calculate Amount' and call handleSetAmountToPAY method which will update amount.
import React, { useState, useMemo, useEffect } from "react";
export default function App() {
//Values
const [serviceItem, setServiceList] = useState([
{ serviceValue: "", quantityValue: "", amountToPay: "" }
]);
console.log("SERVICE ITEM", serviceItem);
//Add item function
const handleItemAdd = () => {
setServiceList([
...serviceItem,
{ serviceValue: "", quantityValue: "", amountToPay: "" }
]);
handleSetAmountToPAY(serviceItem)
};
//Remove item function
const handleItemRemove = (index) => {
const list = [...serviceItem];
list.splice(index, 1);
setServiceList(list);
handleSetAmountToPAY(list)
};
//Get Values
const handleGetValues = (e, index) => {
const { name, value } = e.target;
const list = [...serviceItem];
list[index][name] = value;
setServiceList(list);
};
//Saving state to another state
const [amountToPay, setAmountToPay] = useState([]);
console.log("AMOUNT TO PAY", amountToPay);
const handleSetAmountToPAY = (list) => {
list && list.map((item) =>
setAmountToPay([...amountToPay, item.amountToPay])
);
}
//Add total amount
const procedurePriceTotal = amountToPay.reduce(
(index, value) => (index = index + value),
0
);
console.log("TOTAL PRICE", procedurePriceTotal);
return (
<div className="App">
{serviceItem.map((singleItem, index) => (
<div class="row form-row">
<div class="col-12 col-md-6 col-lg-4">
<div class="form-group">
<label>
Service <span class="text-danger">*</span>
</label>
<input
name="serviceValue"
type="text"
class="form-control"
value={singleItem.serviceValue}
placeholder="Tooth Extraction"
onChange={(e) => {
handleGetValues(e, index);
}}
/>
</div>
</div>
<div class="col-12 col-md-6 col-lg-3">
<div class="form-group">
<label>
Quantity <span class="text-danger">*</span>
</label>
<input
name="quantityValue"
type="text"
class="form-control"
placeholder="1"
value={singleItem.quantityValue}
onChange={(e) => {
handleGetValues(e, index);
}}
/>
</div>
</div>
<div class="col-12 col-md-6 col-lg-3">
<div class="form-group">
<label>
Amount (₱)<span class="text-danger">*</span>
</label>
<input
name="amountToPay"
type="number"
class="form-control"
placeholder="500"
value={singleItem.amountToPay}
onChange={(e) => {
handleGetValues(e, index);
}}
/>
</div>
</div>
<div class="col-12 col-md-6 col-lg-2">
<div class="add-more">
<br />
{serviceItem.length !== 1 && (
<button
type="submit"
onClick={() => handleItemRemove(index)}
className="btn btn-primary rx-pr"
>
<i className="fas fa-plus" /> Remove Item
</button>
)}
</div>
</div>
</div>
))}
{/* Add Item */}
<div className="add-more-item rx-pr">
<button
type="submit"
onClick={handleItemAdd}
className="btn btn-primary rx-pr"
>
<i className="fas fa-plus" /> Add Item
</button>
</div>
</div>
);
}

I was doing it the wrong way.
I solved it by mapping the serviceItem then using reduce to get the sum instead of putting it again into a separate array of object then mapping it again to get the sum.
const newNumberArray = serviceItem.map(function(item) {
return parseInt(item.amountToPay)
})
const totalAmountPaid = newNumberArray.reduce((index,value) => index = index + value, 0 )
Thanks for all the help and suggestion!

Related

.map() React undefined - Axios & Express

Keep getting this error when trying to submit a form in React
Uncaught TypeError: queryList.map is not a function
This is the code:
const [query, setQuery] = useState();
const [queryList, setQueryList] = useState([]);
const [response, setResponse] = useState([]);
const [responseList, setResponseList] = useState([]);
const createQuery = () =>{
setQueryList(
{query}
);
alert(queryList)
Axios.post('http://localhost:3001/createQuery', {
query
}).then((res)=>{
setResponse(res)
setResponseList(...responseList, {res})
})
}
return(
<div>
{queryList && queryList.map((e) => {
return(
<p className="ml-52 text-white text-xl">{e.query}</p>
)
})}
<form>
<textarea onChange={(event)=>{
setQuery(event.target.value)
}}
type="text" name="name" autoComplete="off" placeholder="Ask a question" className = "caret-gray-200 bg-gray-800 shadow-md h-20 w-5/6 inset-x-0 bottom-6 absolute left-36 items-center snap-center text-xl p-6" />
<button onClick={createQuery} type="submit" name="submit" className="text-white inset-x-0 bottom-6 absolute bg-transparent w-20 h-20 ml-auto mr-28 focus:outline-none focus:none">
<AiOutlineSend size="28" />
</button>
</form>
</div>
)
After I submit the form, I need the query mapped
I know I didn't mention any other errors, but if you see them, please let me know!
By the way, all the other posts didn't work for me.
Thanks :)
When you set the queryList you will need to set it to an array by using array brackets. Also by the name i'm assuming you want an array of all queries so you will need to include previous queries that are already stored in queryList.
setQueryList([...queryList, { query }]);
This is what your setter function should look like.
Aside from this the alert function will not work since queryList is not updated in time to be used so I would recommend you to just use query in alert instead of queryList.
Also because of the way you use forms the page will be redirected, this is solved by using onSubmit event and using the preventDefault() function.
const [query, setQuery] = useState();
const [queryList, setQueryList] = useState([]);
const [response, setResponse] = useState();
const [responseList, setResponseList] = useState([]);
const createQuery = (event) => {
event.preventDefault();
setQueryList([...queryList, { query }]);
alert(query);
Axios.post('http://localhost:3001/createQuery', {
query,
}).then((res) => {
setResponse(res);
setResponseList(...responseList, { res });
});
};
return (
<div>
{queryList &&
queryList.map((e) => {
return (
<p className='ml-52 text-white text-xl'>{e.query}</p>
);
})}
<form onSubmit={createQuery}>
<textarea
onChange={(event) => {
setQuery(event.target.value);
}}
type='text'
name='name'
autoComplete='off'
placeholder='Ask a question'
className='caret-gray-200 bg-gray-800 shadow-md h-20 w-5/6 inset-x-0 bottom-6 absolute left-36 items-center snap-center text-xl p-6'
/>
<button
type='submit'
name='submit'
className='text-white inset-x-0 bottom-6 absolute bg-transparent w-20 h-20 ml-auto mr-28 focus:outline-none focus:none'
/>
</form>
</div>
);
I have not tested anything with axios but this code should work.

How to get form data as a object in reactjs

I'm trying to create a google form with react,
I have been creating all the questions as a components
if (props.type == "text") {
return (
<div className="box">
<h3 className="">{props.qustion}</h3>
<input className="short-text" placeholder="Your answer" id={"text"+props.id} name={"q"+props.id} type="text" onChange={updateField}/>
</div>
)
}
else if (props.type == "choice") {
return (
<div className="box">
<h3 className="">{props.qustion}{props.requre? <label className="requir">*</label>:""}</h3>
{props.answer.map(ans=>(
<div key={ans}>
<input className="radio" type="radio" id={ans} name={"radio"+props.id} value={ans} required={props.requre} onChange={updateField}/>
<label htmlFor={ans}>{ans}</label>
</div>
))
}
</div>
)
and I have been creating a form on the app file and put the components inside him,
return (
<div className="App">
<FormTitle/>
<form>
{
error? <h1>the sorce not found</h1>:data.map((item) =>(<Qustion qustion={item.question} type={item.type} requre={item.requre} id={item.id} answer={item.answares} key={item.id} />))
}
<div className="submit-right">
<input className="submit-button" type="submit" value="Submit" />
</div>
</form>
</div>
);
how to get all the form data as an object to create a post request ??
Try this function at start of the file where the form is
const formSubmit = (event) => {
event.preventDefault();
var data = new FormData(event.target);
let formObject = Object.fromEntries(data.entries());
console.log(formObject);
}
and in the form use this onSubmit={formSubmit}
<form onSubmit={formSubmit}>
<any element or components>
</form>
entries is not a function you can just reach it
const formSubmit = (event) => {
event.preventDefault();
var data = new FormData(event.target);
let formObject = Object.fromEntries(data.entries);
console.log(formObject);
}

Trying to display IMG with src set to Binary/Buffer image data from Mongoose

Trying to display IMGs with React/JSX with Buffered/Binary data saved to my MongoDB/Mongoose database.
i iterate over the data array with the IMG element looking like this:
<img src={`data:${item.img.contentType};base64,${item.img.data.data.toString("base64")}`} alt="" />
import React, { useState, useEffect } from "react";
import axios from "axios";
const FilesUpload = () => {
const [allPics, setAllPics] = useState([]);
useEffect(() => {
const getPics = async () => {
let res = await axios.get("http://localhost:8080/");
setAllPics(res.data);
};
// query the server for all of the picture objects
getPics();
}, []);
const [state, setState] = useState({});
const handleChange = (e) => {
e.preventDefault();
setState(e.target.value);
console.log(state);
};
return (
<>
<h1>upload an image</h1>
<hr />
<div>
<form
action="http://localhost:8080/"
method="POST"
encType="multipart/form-data"
>
<div>
<label>Image Title</label>
<input type="text" id="name" placeholder="Name" name="name" onChange={handleChange} value={state.name}/>
</div>
<div>
<label htmlFor="desc">Image Description</label>
<textarea id="desc" name="desc" rows="2" placeholder="Description"
onChange={handleChange} value={state.desc}/>
</div>
<div>
<label htmlFor="image">Upload Image</label>
<input type="file" id="image" name="image" required />
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
{allPics.length > 0 ? (
<div>
{allPics.map((item, index) => (
<div key={index}>
<div>
<img src={`data:${item.img.contentType};base64,${item.img.data.data.toString("base64")}`} alt="" />
</div>
</div>
))}
</div>
) : (
<>
<hr />
<br />
<h1>uploaded files</h1>
<h5>Loading..</h5>
</>
)}
</>
);
};
export default FilesUpload;
but I always get ERR_INVALID_URL:
from similar threads on the net I've read that I need to take those comma-delimitated values and remove the comma which will give me the proper data. having a hard time figuring that out. any help would be great. thanks
I was facing the same problem after saving image like this in my mongoose model and after many research I resolved this, hope this works for you also
const base64String = btoa(String.fromCharCode(...new Uint8Array(item.img.data.data)));
and in img tag put this -:
src={`data:image/${item?.img?.contentType};base64,${base64String}`}

How to add filter method onclick button in react

I have a form which has a input field called admissionNumber and the button. In input field when user enter number and click the button then function getAllStudent filter the an array . If admission number match with entered number then other fields (fullname and faculty) automatically filled . How can I do this ? Please someone help me to do this . Thank you
getAllStudents function which return students details (admissionNumber,fullname,faculty)
getAllStudents(user._id, token).then((data) => {
if (data.error) {
setValues({ ...values, error: data.error });
} else {
setValues(data);
}
});
form fields
<input
type="text"
onChange={(event) => {
setSearchTerm(event.target.value);
}}
className="form-control offset-md-2 col-md-6"
placeholder="Admission Number"
required
maxLength="5"
/>
<button
// onClick={}
className="btn rounded ml-4"
>
Verify
</button>
</div>
<div className="bg-dark rounded">Personal Details</div>
<div className="row form-group ">
<input
type="text"
name="studentFullName"
className="form-control mt-2 offset-md-2 col-md-8"
placeholder="Student Name"
/>
<input
type="text"
name="faculty"
className="form-control mt-2 offset-md-2 col-md-8"
/>
</div>
You should pass a function to button onClick prop.
Assuming you using a functional component and a state with students, currentUser and searchTerm you can do something like that:
const [students] = useState([...])
const [currentUser, setCurrentUser] = useState(undefined)
const [searchTerm, setSearchTerm] = useState(undefined)
const checkStudent = () => {
const match = students.find(student => student.admissionNumber === searchTerm)
if(match) {
setCurrentUser(match)
}
}
return (
<>
<button
onClick={() => checkStudent()}
/>
<input
type="text"
name="studentFullName"
className="form-control mt-2 offset-md-2 col-md-8"
placeholder="Student Name"
value={currentUser?.fullname}
/>
</>
)

How to display value in real time without refresh page with React and SocketIO?

I develop a basic application with NodeJS, React and SocketIO.
My NodeJS server sends socket to the React clients with a table of players (string value). I want display this table of players in the react view, and refresh it dynamically when it changes.
I tried some solutions but nothing works great. Have you ideas to do that or to improve my code ?
Thanks
Constructor : this.players[]
constructor(props){
super(props);
this.state = {
endpoint: "http://127.0.0.1:8080",
}
this.gameId = this.props.match.params.id;
this.players = [];
}
showPlayer : display list of players with cards
showPlayers = () => {
const classes = this.props;
let playersCards = [];
console.log(this.players);
this.players.foreach(function(p){
playersCards.push(
<Card className={classes.card}>
<CardHeader
avatar={
<Avatar style={{backgroundColor: "#00FF00"}} aria-label="Recipe">
R
</Avatar>
}
action={
<IconButton>
<MoreVertIcon />
</IconButton>
}
title={p}
subheader=""
/>
</Card>
)
}
return playersCards;
}
Socket.io : get the table of players updated
socket.on('ack-join-game', function(res){
this.players = res.dataGame.players;
});
Render :
const classes = this.props;
return(
<div className="GameConfig">
<h1>Salon de jeu</h1>
<div className="well" style={this.wellStyles}>
<h2>Informations</h2>
Id : {this.gameId}
<br></br>
<h2>Players (0/2)</h2>
<div id="cards">
</div>
{this.showPlayers()}
<form onSubmit={this.handleFormSubmit}>
<br></br>
<Button bsStyle="primary" type="submit" bsSize="large" block>
Lancer la partie
</Button>
</form>
</div>
<ToastContainer store={ToastStore}/>
</div>
)
}
You should store your players in the state of your component as changing them affects what is going to be rendered. Also, you can remove the endpoint if it is never going to change at runtime :
constructor(props){
super(props);
this.state = {
players = [],
}
this.gameId = this.props.match.params.id;
this.endpoint = "http://127.0.0.1:8080";
}
Then call setState to update players and refresh the component in your socket event :
socket.on('ack-join-game', res => {
this.setState({ players: res.dataGame.players })
});
Now, your players will need to be accessed via this.state.players instead of this.players.
You could also completely remove your showPlayers function using map:
const { players } = this.state
const { card } = this.props.classes
return (
<div className="GameConfig">
<h1>Salon de jeu</h1>
<div className="well" style={this.wellStyles}>
<h2>Informations</h2>
Id : {this.gameId}
<br></br>
<h2>Players (0/2)</h2>
<div id="cards">
</div>
{players.map(player =>
<Card className={card} key={player}>
<CardHeader
avatar={
<Avatar style={{ backgroundColor: "#00FF00" }} aria-label="Recipe">
R
</Avatar>
}
action={
<IconButton>
<MoreVertIcon />
</IconButton>
}
title={player}
subheader=""
/>
</Card>
)}
<form onSubmit={this.handleFormSubmit}>
<br></br>
<Button bsStyle="primary" type="submit" bsSize="large" block>
Lancer la partie
</Button>
</form>
</div>
<ToastContainer store={ToastStore} />
</div>
)

Resources