onClick event doesn't work on first click - React - reactjs

I'm a react newbie. Though, I've resolved the issue, just curious to know why it happens, why onClick sends an empty object to backend on first click. Along with sending the data to database I had to update a field on UI, thus, set a state to do the job. and what is the standard practice in these situations. I mean when I have to update the db as well as show the update instantly
const [item,setItem] = useState([]);
const handleAddProducts = e =>{
e.preventDefault();
const productName = e.target.productName.value;
const price = parseFloat(e.target.price.value);
const quantity = parseInt(e.target.quantity.value);
const image = e.target.image.value;
const data = {productName,price,quantity,image};
const url = 'http://localhost:5000/add';
if(data.productName && data.price && data.quantity && data.image){
fetch(url,{
method:'POST',
headers:{
'content-type':'application/json'
},
body:JSON.stringify(item)
})
.then(res=>res.json())
.then(result=>{
if(result.insertedId){
toast('Product Added Successfully');
e.target.reset();
}
})
}
else{
toast("Nothing to Upload");
}
}

I think your handleAddProducts is getting called on an onFormSubmit event, please add a full code snippet so we can help you better.
I'll assume your handleAddProducts function is getting called on a form submit
const handleAddProducts = (e) => {
e.preventDefault()
let { productName, price, quantity, image } = new FormData(e.target)
price = parseFloat(price)
quantity = parseInt(quantity)
const url = 'http://localhost:5000/add'
if (productName && price && quantity && image) {
fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(item),
})
.then((res) => res.json())
.then((result) => {
if (result.insertedId) {
toast('Product Added Successfully')
e.target.reset()
}
})
} else {
toast('Nothing to Upload')
}
}
e.target when a form gets submitted doesn't include the inputs, it holds information about the <form> HTML element itself, and there's no way you can read the inner form values except by using something such as new FormData(e.target).
Talking about how usually React Devs manage these kinds of stuff, this is called inputs state management.
Basically, you keep recording every change that occurs to an input element in a separate state.
ex:
const myComponent = () => {
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [phoneNum, setPhoneNum] = useState('')
return (
<form onSubmit={handleSubmit}>
<input onChange={({ target }) => setFirstName(target.value)} value={firstName} />
<input onChange={({ target }) => setLastName(target.value)} value={lastName} />
<input onChange={({ target }) => setPhoneNum(target.value)} value={phoneNum} />
</form>
)
}

Related

Unable to update hook after image is uploaded to firebase

First of all, thanks for the help...I'm quite new to React and I have NO IDEA why won't my hook update.
So I want to upload an image to firebase, grab the url from firebase, update it with the hook and send it to my db.
const [image, setImage] = useState(null);
const [imageURL, setImageURL] = useState({ preview: null, data: null });
// show preview every time the input changes
useEffect(() => {
// ensure image is present on the page and NOT null
if (image) setImageURL({ preview: URL.createObjectURL(image), data: null });
}, [image]);
const handleSubmit = async e => {
e.preventDefault();
try {
if (image) {
// create reference point to where the image is uploaded to
const imageRef = ref(storage, `${Date.now()} -- ${image.name}`);
// upload image & retrieve public url to save in db
await uploadBytes(imageRef, image);
const imageUrl = await getDownloadURL(imageRef);
setImageURL(prev => ({ ...prev, data: imageUrl })); <-- can't update hook
}
...other code
} catch (err) {
... handle error
}
<form onSubmit={handleSubmit}>
<label>
<span>Upload an image</span>
<input
type='file'
accept='image/*'
onChange={e => setImage(e.target.files[0])}
/>
</label>
</form>
I am using React 18 and Firebase 9.
I'm not sure what I'm doing wrong...so any help is greatly appreciated!
I have tried using async/await and then/catch, but both no luck
I don't know if I'm doing the right thing, but instead of updating the state, I simply post the returned URL from firebase to the db
Updated code:
const [image, setImage] = useState({ file: null, previewUrl: null });
useEffect(() => {
if (image.file)
setImage(prev => ({
...prev,
previewUrl: URL.createObjectURL(image.file),
}));
}, [image.file]);
const handleSubmit = async e => {
e.preventDefault();
let uploadedImage = null;
try {
if (image.file) {
uploadedImage = await uploadImage(image.file);
}
const res = await axios.post('/api/drinks', {
image: uploadedImage,
});
console.log(res);
} catch (err) {
...handle error
}
}
Everything works fine now, but again, I'm not sure if this is the right way to do it, so any feedbacks/comments are appreciated!

Conditional Routing in React based on API calls

So I'm trying to create a React web app with multiple pages and connecting it to Flask to fetch data using the fetch API. Here is what I want to achieve:
If the user submits a Form, React does a POST request to the Flask API which returns a JSON object, which is received by React and I render the predict route. This is handled using the Forms.jsx component, which has the following code:
const Form = () => {
const [title, setTitle] = useState("");
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
const movie_submit = {title};
console.log(movie_submit);
fetch('/predict', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(movie_submit)
}).then(() => {
(navigate("/predict"));
})
}
return (
<div className='form_container'>
<form className='form' onSubmit={handleSubmit}>
<input type='text' placeholder='Movie Name' autoFocus
autoComplete='off' value={title} onChange={(e)=>setTitle(e.target.value)}/>
<button className='button'>Recommend!</button>
</form>
</div>
)
}
export default Form
Now I want to perform a GET request to the Flask API to get what should be put into the Predict.js page (/predict route), and the show it.
Predict.js is as:
const Predict = () => {
const [movies, setMovies] = useState([]);
useEffect(() => {
fetch('/predict').then(response =>
response.json().then(data =>
{
setMovies(Object.values(data));
}))
}, []);
const movie_name = movies.map((movie) => <p key={movie.toString()}>{movie}</p>);
return (
<div>
<Navbar />
<h1>Predictions</h1>
<br />
<h2><Movies movie={movie_name}/></h2>
</div>
)
}
export default Predict
But I want this to be such that if the user hasn't submitted the form, then it navigates to /apology route, and if the FLASK API GET request returns an empty object, even then it navigates to /apology route. How do I do this? I understand this is conditional routing of some sort, but I havent been able to quite achieve where I should do this. Here <Movies /> is simply a component that helps in showing the movie names
You can pass a data to the state prop of the location object.
fetch('/predict', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(movie_submit)
}).then(() => {
(navigate('/predict', { state: { wasFetched: true } }));
})
then in your Predict Component:
const { state } = useLocation();
const { wasFetched } = state;
useEffect(() => {
if (wasFetched) {
// user submited the form
} else {
// user hasn't submited the form
}
}, [wasFetched]);

How do I make the API rerender and update the component when the search field is updated in React?

I have a page in my React Gatsby project where I retrieve data from an API and the page renders the details from the API.
I am trying to add a search feature where the API re-renders when the input search field is updated, but it does not work.
Below is the code:
const [search, setSearch] = useState("")
const [people, setPeople] = useState()
let myHeaders = new Headers();
const getPeople = async () => {
myHeaders.append("Access-Control-Request-Headers", process.env.GATSBY_PEOPLE_ACCESS_CONTROL);
myHeaders.append("Authorization", process.env.GATSBY_PEOPLE_BEARER);
const requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
try {
let response = await fetch(
process.env.GATSBY_PEOPLE_API + "&search=" + search, requestOptions);
let result = await response.json();
setPeople(result)
} catch (err) { console.error(err); }
};
const searchUpdate = (e) => {
setSearch(e.target.value)
}
useEffect(() => {
getPeople()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<Fragment>
<input placeholder="Search.." onChange={searchUpdate}></input>
<div className="people">
{people ? Object.values(people).map(person => {
return (
<div id={"person-"+person.id} key={person.id} >
{person.name}
</div>
)
}) : "Not available.."}
</div>
</Fragment >
)
How do I make the search feature work?
Add search as a dependency in the useEffect method because when the search state change useEffect is called.
useEffect(() => {
getPeople()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [search])

React POST request, how to reconcile a timing error?

I have a react file tied to an express/node backend and SQL database. My backend is functioning correctly, all routes are verified with postman and the app has the ability to get, post, update, and delete. However, I am running into an issue with my front end now, specifically regarding my POST request.
Every time I make a post request the server/database are being updated correctly with the new applicant, which I can see populate the back end, but I am receiving the following error on the front end "Cannot read properties of undefined (reading 'store')" pertaining to my function labeled "TableList"
Somehow the TableList function which has the role of extrapolating only the unique stores that applicants are assigned too is picking up an "undefined" value for the new store assignment whenever a POST request is made. What is most confusing to me is that if I then reload the page manually the front end displays correctly. Is this a timing issue related to async?
Below is my main file where all state is held - relevant functions are TableList and those containing the markup New Vehicle Form
import { useState, useEffect, useImperativeHandle } from "react";
import useFetch from "../Components/Fetch/fetch";
import List from "../Components/Sections/list";
import Intro from "../Components/Sections/intro";
import SearchForm from "../Components/Forms/searchForm";
import AddForm from "../Components/Forms/addForm";
import UpdateForm from "../Components/Forms/updateForm";
import { parseDate } from "../Components/Utils/index";
const Home = () => {
/* DATE & TIME FORMAT */
var today = new Date();
const displaytime = parseDate(today);
/*INITIAL STATE*/
const { data, setData, isPending } = useFetch(
`http://localhost:5000/api/applicants`
);
/*NEW VEHICLE FORM: TOGGLE FORM DISPLAY*/
const [showAddForm, setShowAddForm] = useState(false);
const handleAddForm = () => {
setShowAddForm(!showAddForm);
};
/*NEW VEHICLE FORM: POST REQUEST, DECLARING STATE*/
const initialFormState = {
store: "",
first_name: "",
last_name: "",
position: "",
recruiterscreen_status:"",
testing_status:"",
interview_status:"",
backgroundcheck_status:"",
drugscreen_status:"",
paperwork_status:"",
date_in: "",
currentdate: displaytime,
notes:"",
};
const [formData, setFormData] = useState({ ...initialFormState });
/*NEW VEHICLE FORM: POST REQUEST, UPDATING STATE*/
const handleFormChange = (event) => {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value,
});
};
/*NEW VEHICLE FORM: POST REQUEST, TRIGGER RERENDER*/
const confirmpost = (applicant) => {
setData([...data, applicant])
console.log(data)
};
/*NEW VEHICLE FORM: POST REQUEST, SUBMIT TO SERVER*/
const handleFormSubmit = (event) => {
event.preventDefault();
const applicant = formData;
console.log(applicant);
fetch(`http://localhost:5000/api/applicants/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(applicant),
})
.then((response) => response.json())
.then((response) => console.log("Added Successfully"))
.then((applicant) => confirmpost(applicant))
.then(()=>handleAddForm())
.catch((error) => console.log("Form submit error", error));
setFormData({ ...initialFormState });
};
/*DELETE APPLICANT: FRONT END RERENDER*/
const deleteApplicant = (id) => {
const updatedTable = data.filter((item) => item.applicant_id != id);
setData(updatedTable);
};
/*DELETE APPLICANT: SERVER REQUEST*/
const handleDelete = (id, stock) => {
fetch(`http://localhost:5000/api/applicants/${id}`, {
method: "DELETE",
})
.then((response) => console.log("Deleted Applicant"))
.then(() => deleteApplicant(id));
};
/*UPDATE FORM: TOGGLE FORM DISPLAY, ALSO GRAB USER ID*/
const [showUpdateForm, setShowUpdateForm] = useState(false);
const [selectedApplicant, setSelectedApplicant] = useState(null)
const [selectedApplicantName, setSelectedApplicantName] = useState(null)
const handleUpdateForm = (applicant_id, first_name,last_name) => {
setSelectedApplicant(applicant_id)
setSelectedApplicantName(first_name + " "+ last_name)
setShowUpdateForm(!showUpdateForm);
console.log(`Show Form: ${showUpdateForm}`)
};
/*UPDATE FORM: DECLARE INITIAL STATE*/
const initialStatusState = {
recruiterscreen_status:null,
testing_status: null,
interview_status:null,
backgroundcheck_status: null,
drugscreen_status: null,
paperwork_status:null,
notes:null,
};
/*UPDATE FROM: CHANGE APPLICANT STATE*/
const [statusData, setStatusData] = useState({ ...initialStatusState });
const handleStatusChange = (event) => {
const { name, value } = event.target;
setStatusData({
...statusData,
[name]: value,
});
};
/*UPDATE FORM: SUMBIT TO SERVER*/
const handleUpdate = (id) => {
fetch(`http://localhost:5000/api/applicants/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(statusData),
})
.then((response) => response.json())
.then(() => confirmUpdate(id))
.then((response) => console.log(`Updated Successfully to
recruiterscreen_status: ${statusData.recruiterscreen_status},
testing_status: ${statusData.testing_status},
interview_status: ${statusData.interview_status},
backgroundcheck_status: ${statusData.backgroundcheck_status},
drugscreen_status: ${statusData.drugscreen_status},
paperwork_status: ${statusData.paperwork_status},
notes:${statusData.notes},`));
setStatusData({ ...initialStatusState });
};
/*UPDATE FORM: FRONT END RERENDER*/
const confirmUpdate = (id) => {
const updatedTable = data.map((item) =>
item.applicant_id != id
? item
: {
...item,
recruiterscreen_status: statusData.recruiterscreen_status,
testing_status: statusData.testing_status,
interview_status: statusData.interview_status,
backgroundcheck_status: statusData.backgroundcheck_status,
drugscreen_status: statusData.drugscreen_status,
paperwork_status: statusData.paperwork_status,
notes:statusData.notes,
}
);
setData(updatedTable);
handleUpdateForm(id)
};
/* NOTES POP UP */
const [notesIsOpen, setNotesIsOpen] = useState(false)
const togglePopup = () => {
setNotesIsOpen(!notesIsOpen)
}
/*LIST OF ACTIVE STORES*/
const unique = (value, index, self) => {
return self.indexOf(value) === index;
};
const TableList = (data) => {
const list = data.map((element) => element.store);
const uniquelist = list.filter(unique).sort();
console.log(uniquelist)
return uniquelist;
};
const stores = TableList(data)
/*RUN*/
return (
<div>
<Intro displaytime={displaytime} />
<div className="add-applicant">
<button className="add-applicant-btn" onClick={handleAddForm}>
Add Applicant
</button>
</div>
<SearchForm data={data} setData={setData} />
{showAddForm ? (
<AddForm
formData={formData}
setFormData={setFormData}
handleFormChange={handleFormChange}
handleFormSubmit={handleFormSubmit}
/>
) : null}
{showUpdateForm ? (
<UpdateForm data={data} selectedApplicant={selectedApplicant} selectedApplicantName={selectedApplicantName} handleUpdateForm={handleUpdateForm} statusData={statusData} handleStatusChange={handleStatusChange} handleUpdate={handleUpdate} />
) : null}
<hr></hr>
{!isPending ? (
stores.map((element) => (
<div>
{" "}
<List
cut={element}
data={data}
isPending={isPending}
handleDelete={handleDelete}
handleUpdate={handleUpdate}
handleStatusChange={handleStatusChange}
showUpdateForm={showUpdateForm}
handleUpdateForm={handleUpdateForm}
togglePopup={togglePopup}
notesIsOpen={notesIsOpen}
TableList={TableList}
/>
</div>
))
) : (
<div>Loading</div>
)}
</div>
);
};
export default Home;
In the fetch chain the logging step which injects an undefined into the chain.
The logging step needs to pass on its input data:
fetch(`http://localhost:5000/api/applicants/`, { }) // Elided
.then((response) => response.json())
.then((response) => {
console.log("Added Successfully")
return response
)
.then((applicant) => confirmpost(applicant))

Unable to update state variables after fetch in ReactJS

On form submit, a new event is added in the Database and the details of this new event are returned back via JSON. I want to concatenate this new JSON in the 'events' data so that the 'events' data gets updated automatically. But it is not working. Here is my code. Please see the line commented as //Here. It is where I am trying to update the event state variable but it is not getting updated.
import React, { useState,useEffect } from 'react';
async function getAllEvents() {
return fetch('http://localhost:8080/get-all-events/', {
method: 'GET',
//body: JSON.stringify(credentials)
})
.then(
data => data.json()
)
}
export default function Dashboard() {
const [events, setEvents ] = useState({});
const [eventName, setEventName] = useState();
useEffect(() => {
getEventsWithFetch();
}, []);
const getEventsWithFetch = async () => {
const response = await fetch("http://localhost:8080/get-all-events/");
const jsonData = await response.json();
setEvents(jsonData);
};
const addEvent = async (name) => {
const response = await fetch("http://localhost:8080/create-event/",
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"title": name,
"day": "1",
"month": "2"
})
}
).then((response) => response.json())
.then((json) => {
events.push(json);
var events_array = events;
setEvents(events_array); //Here
})
};
const handleSubmit = async e => {
e.preventDefault();
var data = new FormData(e.target)
}
return(
<div>
<p>EVENTS DASHBOARD</p>
{JSON.stringify(events)}
<form onSubmit={handleSubmit}>
<label>
<p><b>Event Name</b></p>
<input type="text" name="event_name" onChange={e => setEventName(e.target.value)} />
</label>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
);
}
You're modifying the read-only state events, instead of using setEvents, which is already a mistake.
Furthermore, the diff-check React does is by reference for objects, so you have to clone events, instead of setting the state to itself:
setEvents([...events, json]); // Using spread to clone the array and add a new element in the end
Otherwise, React sees the same reference being set and ignores it.

Resources