Unable to update state variables after fetch in ReactJS - 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.

Related

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]);

React and Websocket messaging to server

So I'm having issues with a single component that displays a list pulled from a resource server. Then it uses Stompjs to establish a websocket and send messages. When I load the client, the Dev Console shows logs that it tries to call onConnected method() twice as my logs show two newUser messages sent from a single load of the component.
When I try to call the submitBid() method it throws a type error saying that
"TypeError: Cannot read properties of undefined (reading 'send')
at submitBid (AuctionList.js:76:1)"
Which I'm not sure why it would be undefined on that line when it's defined and running fine in the function on line 36 which runs before the method that fails. I've been stuck on this for several days so hopefully someone can tell me what I've got wrong in the code... Here is the component code....
import React from 'react'
import Stomp from 'stompjs';
import SockJS from 'sockjs-client';
import {useState, useEffect } from 'react';
function AuctionList({user, authCredentials, token}) {
const [auctionItems, setAuctionItems] = useState([]);
const [userData, setUserData] = useState({
email: user.email,
name: user.name,
message: ''
});
const [bid, setBid] = useState(0.00);
let stompClient;
let socket;
const connect = async () => {
socket = new SockJS('http://localhost:8080/ws')
stompClient = Stomp.over(socket)
stompClient.connect({}, onConnected, onError)
}
const onConnected = async () => {
stompClient.subscribe('/topic/bids', onMessageReceived)
stompClient.send("/app/socket.newUser",
{},
JSON.stringify({
sender: user.name,
type: 'NEW_USER',
time: Date.now()
})
)
}
const onError = async (err) => {
console.log(err);
}
const handleChange = async (e) =>{
setBid(e.target.value);
}
const submitBid = async (item) => {
let newMessage = {
type: "BID",
newBid: {
itemId: item.id,
email: user.email,
bidPrice: bid,
bidTime: new Date().getTime()
},
sender: userData.email,
time: new Date().getTime()
};
try {
stompClient.send("/socket.send", {}, JSON.stringify(newMessage));
} catch(err){
console.log(err); }
}
const onMessageReceived = async (payload)=>{
console.log("onMessageReceived")
console.log(payload)
}
const getAuctionList = async () => {
const url = "http://localhost:8080/auctionlist";
const init = {
method: "GET",
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`, // notice the Bearer before your token
},
};
fetch(url, init)
.then(response => response.json())
.then(response => {setAuctionItems(response)})
};
useEffect( () => {
getAuctionList();
connect();
}, []);
return (
<ul>
{auctionItems.map( item => {
return( <div key={item.id} className = "auctionItemComponent">
<h3>{item.name}</h3>
<span>{item.desc}</span>
<span>Current Bid: ${item.itemStartingPrice}</span>
<span>Minimum Bid: {item.itemMinBid}</span>
<span>Time left</span>
<input type="number" id="bidInput_" name="bidInput" onChange={handleChange} ></input>
<button type='submit' onClick={submitBid}>Submit bid</button>
</div>)
})}
</ul>
)
}
export default AuctionList
Also I realize I have a bunch of async functions that don't have any awaits. I tried adding those in, but it was no change.
The issue here is not with stompjs but with the scoping. You have stompClient inside React Component but the one from submitBid is different. You can do it in different ways.
Put stompjs in global stage as in example here: https://playcode.io/972045
You can use useRef to have the client inside the React Component and have React do the tracking of any modifications.
I personally think something like a "connection" should stay away from inside a React Component. You should have the connection configs in a different file and import an instance to the JSX file.

json response from mock server not printing but is in the console

I am trying to learn react, and I am making a successful API call, but it only prints in the console. I found examples but many of them recommended to use setData(json) but I am not able to use it because the file is a list of export async function which was also recommended.
export async function GetHellWorld() {
return fetch(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => {
console.log(json)
})
.catch(error => (console.log(error)))
}
and the component
function Test(thisArg, argArray) {
const result = GetHellWorld.apply()
return (
<div className="App">
{JSON.stringify(result)}
</div>
);
}
export default Test;
In the console I see "Hello World" but in the browser is get just {}.
Two questions:
How can I bind the JSON response to an object so I can do something like result.name.
Is this the correct was to call the await function? const result = GetHellWorld.apply()
---- update ----
I decided to try axios because I want to make multiple calls in one file.
const axios = require('axios');
export class AppService {
public async GetHelloWorld(): Promise<any> {
const response = await axios.get(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).catch(() => console.log("Issue in GetHelloWorld"))
return response.data
}
}
component
import React from 'react';
import {AppService} from "../services/app.service";
function Movies() {
const api = new AppService()
const hello = async () => {
const response = await api.GetHelloWorld();
console.log("The response: " + response)
}
return (
<div className="App">
{JSON.stringify(hello)}
</div>
);
}
note I had to add typescript support.
For whatever reason I get
Module not found: Error: Can't resolve '../services/app.service' in '/Users/miketye/programming/test-react/src/components'
While the other answer about using a custom hook can work, I would not recommend it while you're still leaning React.
Look up how to use the "useEffect" hook, that's generally how you want to do any sort of loading logic in React.
First off, you need to fix your async function so it actually returns a value:
// style/convention note, but non-component functions should not start with a capital letter
export async function getHelloWorld() {
return fetch(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => {
return json // will cause this function to return a Promise of type "string", since we're in an async function
})
// better to just let the error get thrown here, for testing
}
Then use it like this:
function Test(thisArg, argArray) {
[fetchResult, setFetchResult] = useState(undefined) // look up useState. State is how you have values that change over time in a resct component
useEffect(() => {
async function fetchData() {
const data = await getHelloWorld()
setFetchResult(data)
}
fetchData()
}, [])
// look up useEffect. Since the second argument (the "dependency array") is empty, useEffect will fire only once, after the component loads
return (
<div className="App">
{result ? JSON.stringify(result) : "no result yet"}
</div>
);
}
export default Test;
You can use a custom hook for this purpose:
import { useState } from "react";
const useFetchData = () => {
const [data, setData] = useState(null);
const fetchData = () => {
fetch("http://localhost:8080/api", {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => { setData(json); })
.catch(error => { console.log(error); })
}
useEffect(() => {
fetchData();
}, []);
return { data, fetchData };
}
export default useFetchData;
And then call it in your component:
import useFetchData from "#/hooks/useFetchData";
const Test = () => {
const { data, fetchData } = useFetchData();
// CALL fetchData IF YOU WANT TO UPDATE THE CURRENT STATE
return (
<div className="App">
{data && JSON.stringify(data)}
</div>
);
}
export default Test;

onClick event doesn't work on first click - React

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

How to add object attribute using useState (react hook) from different state that is set by children?

I'm new to React and I hope it is not a duplicate question here, somewhere and sorry if my explanation is wrong. So, I got this 'createEvent' panel that has 3 children's; name, start date (comes from a calendar component), end date (also) and details that come from the input text area. I created 'createEvent' panel as a parent that contains states for every data that come from the child. Inside the parent component, I want to create an object that can be sent to the backend. I tried to get data from children and using useState() to give values to the object attributes. If I try to get data using input from parent it works, but not getting input from children and I don't know what to do.
I`ll leave here examples of one child, create Event panel and post request. If I figure out how to do with these examples, I'll know to do it forward.
insert name component
import React, { useState } from 'react';
import './title.style.css';
export default function Title(props) {
const [name, setName] = useState(props.name);
function handlerName(e) {
props.handlerName(e.target.value);
}
return(
<div class="md-form active-cyan active-cyan-2 mb-3">
<input
class="form-control"
type="text"
placeholder={name}
aria-label="Name"
onChange={(e) => {handlerName(e)}}
/>
</div>
);
}
create event panel
import React, { useState, useEffect } from 'react';
import './createEvent.style.css';
import Name from '../input/title.component.js';
import Calendar from '../input/calendar.component.js';
import Details from '../input/details.component.js';
import CardService from '../../service/CardService.js';
export default function CreateEvent() {
const [eventName, setEventName] = useState("");
const [eventStartDate, setEventStartDate] = useState(new Date());
const [eventEndDate, setEventEndDate] = useState(new Date());
const [eventDetails, setEventDetails] = useState("");
const [eventObject, setEventObject] = useState({
"name": "",
"startDate": new Date(),
"endDate": new Date(),
"details": ""
});
async function handleName(name1){
await setEventName(name1);
}
async function handleStartDate(date){
await setEventObject({startDate: date});
}
async function handleEndDate(date){
await setEventObject({endDate: date});
}
async function handleDetails(value){
await setEventObject({details: value});
}
function handleSubmit(e) {
CardService.postEvent(eventObject)
}
return(
<form onSubmit={(e) => handleSubmit(e)}>
<div className="Create-event">
<div>
<Name name={'Insert event name'} handlerName={handleName} />
</div>
<div className="date">
<Calendar type="submit" handlerDate={handleStartDate} date={eventStartDate}/>
</div>
<div className="date">
<Calendar type="submit" handlerDate={handleEndDate} date={eventEndDate}/>
</div>
<div className="text">
<Details type="submit" handleDetails={handleDetails}/>
</div>
<button className="createButton" >
Create
</button>
</div>
</form>
);
}
post request
const CardService = {
postEvent(value1){
fetch("http://localhost:8080/event/ReminderApp/api/v1/event/" ,
{
method: 'POST',
headers: {
"Content-Type" : "application/JSON" ,
},
dataType: "json",
body: JSON.stringify(value1),
})
.then(response => response.json())
.then(json => {console.log(json)
})
.catch(err => {
console.log('Type send failed', err);
});
},
getEvents() {
return fetch('http://localhost:8080/event/ReminderApp/api/v1/event')
.then((response) => response.json())
.then((response) => {
return response;
});
},
deleteEvent(value) {
fetch("http://localhost:8080/event/ReminderApp/api/v1/event/" + value,
{
method: 'DELETE',
})
.catch(err => {
console.log('Type send failed', err);
});
},
};
export default CardService;
I tried to set all createEvent states in handle function and after using useEffect to set all fields from the object, but the app got broken. I tried to setEventObject() inside the handleSubmit function and call CardService.postEvent() but it sent the default object.
What I show you now, it is the last version of my attemps.

Resources