React POST request, how to reconcile a timing error? - reactjs

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

Related

useSWRMutation stale state in onSuccess callback expo

When I console.log phoneNumber in onSuccess, it shows an empty string instead of the correct value. phoneNumber is stale. When I type something in my editor which makes the app rerender, phoneNumber has the correct value. It seems the function in onSuccess sort of gets captured by the closure for some reason? I'm not sure why.
const verifyPhoneNumber = async (url: string, { arg: phoneNumber }) => {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
phone_number: phoneNumber,
}),
});
const json = await response.json();
return json;
} catch (e) {
console.log(e);
}
};
const SignUp = (props: Props) => {
const navigation = useNavigation();
const [phoneNumber, setPhoneNumber] = useState("");
const url = "https://jsonplaceholder.typicode.com/posts/";
// const url = "http://192.168.0.142:8000/user/auth/otp-code/";
const { trigger, isMutating } = useSWRMutation(url, verifyPhoneNumber, {
onSuccess: () => {
// The problem is here, phoneNumber is an empty string
console.log(phoneNumber)
navigation.navigate("VerifyCode", {
phoneNumber: phoneNumber,
});
},
onError: (error) => console.log(error),
});
const sendOTP = () => {
trigger(phoneNumber);
};
// phoneNumber shows the proper string
console.log(phoneNumber);
return (
<AppBox>
<InputFieldForAuth
inputIsInvalid={false}
value={phoneNumber}
onChangeText={(newText: string) => setPhoneNumber(newText)}
placeholder={`Enter your phone number here`}
/>
</HyperlinkText>
<Button
text="Continue"
onPress={sendOTP}
/>
</AppBox>
);
};
I even tried something like this to make the function get recreated with a new phoneNumber each render, and phoneNumber is still stale
const onSuccess = useCallback(() => {
console.log(phoneNumber);
navigation.navigate("VerifyCode", {
phoneNumber: phoneNumber,
});
}, [phoneNumber]);
const { trigger, isMutating } = useSWRMutation(url, verifyPhoneNumber, {
onSuccess: onSuccess,
onError: (error) => console.log(error),
});
A snack is attached at the bottom demonstrating the issue.
https://snack.expo.dev/#meg_hidey/uplifting-churros

React: How to use received json data from firebase to make list of array?

I want to make a list of user details by filling the form with name, email, designation, phone & image input field, I've saved the form data in an object and sent the form data to it's parent using props addedUser(userData);, I'm using this addedUser prop in the parent component to send the data to the firebase and then use the same fetched data to make a list of users in array.
I've made loadedData empty array simply wanna push the data into it but the responseData.json() doesn't give me anything, the data is being saved to firebase perfectly but I'm facing problems using it.
Please check the code here and also the <Users /> component code where I'm trying to make the list:
Parent:-
function App() {
const [userData, setUserData] = useState([]);
const fetchData = useCallback(async () => {
try {
const responseData = await fetch(
"https://react-users-db-default-rtdb.asia-southeast1.firebasedatabase.app/users.json"
);
const data = responseData.json();
console.log(data);
const loadedData = [];
for (const key in data) {
loadedData.push({
key: key,
userName: data[key].userName,
userEmail: data[key].userEmail,
userDesignation: data[key].userDesignation,
usePhone: data[key].usePhone,
userImage: data[key].userImage,
});
}
console.log(loadedData);
setUserData(loadedData);
} catch (err) {
console.log(err);
}
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
async function recieveUserData(users) {
const responseData = await fetch(
"https://react-users-db-default-rtdb.asia-southeast1.firebasedatabase.app/users.json",
{
method: "POST",
body: JSON.stringify(users),
headers: {
"Content-Type": "application/json",
},
}
);
const data = await responseData.json();
console.log(data);
}
return (
<main>
<div className="form__wrap">
<AddUserForm addedUser={recieveUserData} />
</div>
<div className="user__wrap">
{userData.length > 0 ? (
<Users newUser={userData} />
) : (
<p>No user found</p>
)}
</div>
</main>
);
}
Users Component:
export default function Users({ newUser }) {
console.log(newUser);
return (
<div className="usercard__wrap">
{newUser.map((el, i) => {
return (
<UserCard
key={i}
name={el.userName}
email={el.userEmail}
designation={el.userDesignation}
phone={el.userPhone}
image={el.userImage}
/>
);
})}
</div>
);
}
Data is going to firebase:-
But responseData.json() is giving me this, I can't see my user object to access:-
In your fetchData function you need to await responseData.json().
const data = await responseData.json();

First time useState not showing up on UI

My simplified code is below.
Goal: To display the names of the images that the user selects. The user selects an image, it is saved to a service, and I use a "put" request to get the images for that particular object, and then display the name. These steps are required for getting the names.
Issue: When I first select images via the "file upload button" (Form.Control), the image name(s) do not display on the #1 (commented as "#1: HERE!", at the bottom of code). When I go back and select more images, then #1 correctly displays the previously selected images AND the newly selected images (which is the correct behavior). My problem is that on the first selection, the image name is NOT being displayed.
const MyComponent = () => {
const [fileNames, setFileNames] = useState({});
// a service that calls gets the file names from objectId
const getFileNames = (objectId) => {
const input = { objectId: objectId };
fetch(service(), {
method: "PUT",
headers: {..(stuff here), "Content-Type": "application/json"},
body: JSON.stringify(input)
})
.then( res => { res.json() })
.then( resJson => {
if( //resJson has errors) {
//log error msg
else {
// problem is here????
setFileNames(resJson.results);
return resJson;
}
})
.catch( err => //stuff here)
}
const handleFileChange = (e) => {
// saves selected file(s) to particular objectId
saveFile(event.target.files), props.objectId, props.somethingelse);
getFileNames(props.objectID);
}
const showName = (file) => {
return (
<span> {file.name} </span>
)
}
return (
<div>
<Form.Control
type="file" multiple
onChange={ e=> {handleFileChange(e)} }
{/* bunch of the stuff here */}
/>
{/* ***********#1 - HERE!*************** */}
{fileNames.map( file => file.showName(file) )}
</div>
)
}
I suspect you are not waiting for the file to be saved before you get the filename. That's why perhaps the first save is empty?
const MyComponent = () => {
const [fileNames, setFileNames] = useState([]);
// This should be an array be default since you map over it later down
// make this an async function
const getFileNames = async (objectId) => {
const input = { objectId: objectId };
let resJson;
try {
const result = await fetch(service(), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
resJson = await result.json();
} catch (error) {
// TODO: error handling
}
if (resJson.error) { // TODO: fix me
//log error msg
} else {
setFileNames(resJson.results);
}
};
// this too should be an asnyc function
const handleFileChange = async (event) => {
// I assume saveFile is an async function
await saveFile(event.target.files, props.objectId, props.somethingelse);
// save the file here ^ and wait for it to finish saving
await getFileNames(props.objectID);
// after you saved the file get the filename now
};
const showName = (file) => {
return <span> {file.name} </span>;
};
return (
<div>
<Form.Control
type="file"
multiple
onChange={(e) => {
handleFileChange(e);
}}
/>
{/* bunch of the stuff here */}
{fileNames.map((file) => file.showName(file))}
</div>
);
};

Trying to modify a data from a React Promise Response changes globally

I have created a codesandbox with a simplified version of my problem
https://codesandbox.io/s/new-react-context-api-ei92k
I get something from a fetch (in this case a user)
I then create a local copy of this user and make some changes to it
The problem: Any changes update my initial user object
Can someone tell me how this is possible? and how can I avoid this?
import React, { useState, useEffect } from "react";
import { AppSessionContext } from "./AppContext";
import Header from "./Header";
const user = {
userName: "jsmith",
firstName: "John",
lastName: "Smith",
isAdmin: true
};
const loadProfile = () => Promise.resolve(user);
function createUserWithNewName(userToUpdate) {
userToUpdate["userName"] = "Dummy";
return userToUpdate;
}
const App = () => {
const [user, setUser] = useState({});
const [Loaded, setLoaded] = useState(false);
var amendedUser = {};
useEffect(() => {
loadProfile()
.then(user => {
setUser(user);
console.log(user);
})
.then(() => {
amendedUser = createUserWithNewName(user);
console.log(amendedUser);
console.log(user);
})
.then(setLoaded(true));
}, []);
if (!Loaded) {
return "Loading";
}
return (
<AppSessionContext.Provider value={{ user }}>
<div className="App">
<Header />
</div>
</AppSessionContext.Provider>
);
};
export default App;
snippet of production code
loadTableDefault() {
fetch(defaultUrl(), {method: 'GET'})
.then(res => res.json())
.then(response => {
this.setState({
data: response,
})
return response
})
.then(response => {
this.setState({
table_data: formatResponsePretty(response),
})
})
.catch(error => console.error('Error:', error));
}
formatResponsePretty
export function formatResponsePretty(oldData) {
const newData = {
...oldData,
};
// consider re-writting the flask response to this format
const obj = { allocations: [] };
var theRemovedElement = ''
var ports = []
ports = Object.values(newData['allocations']['columns']);
ports.shift();
var dataArray = ['allocations', 'conditions', 'liquidity', 'hedging']
for (const index of dataArray) {
for (const i of newData[index]['data']) {
theRemovedElement = i.shift();
if (index === 'allocations') {
obj[index][theRemovedElement] = i
}
else {
obj[theRemovedElement] = i;
}
}
}
const rows = []
let index = 0;
Object.keys(obj).forEach(element => {
index = formatting.findIndex(x => x.name === element)
if (formatting[index] && formatting[index]['type'] === 'number') {
var new_obj = obj[element].map(function (el) {
return Number(el * formatting[index]['multiplier']).toFixed(formatting[index]['decimal']) + formatting[index]['symbol']
})
rows.push(new_obj)
}
else if (formatting[index] && formatting[index]['type'] === 'string') {
rows.push(obj[element])
}
else if (formatting[index] && formatting[index]['type'] === 'boolean') {
// there should be logic here to display true or false instead of 1 and 0
// this could be in the upload
rows.push(obj[element])
}
else {
rows.push(obj[element])
}
})
const arrOfObj = createRecords(ports, rows)
return {obj: obj, ports: ports, rows: rows, arrOfObj: arrOfObj}
}
In createUserWithNewName() you are updating the original user object and returning it.
You instead want to create a new object with all the old user properties, but with just the username changed. Thankfully, object destructuring makes this super easy:
function createUserWithNewName(oldUser) {
const newUser = {
...oldUser,
userName: 'Dummy',
};
return newUser;
}
This will copy all the properties of oldUser to a new object and then just update userName!
You're also going to want to pass user down to that second .then() as it won't currently be available in there:
.then(user => {
setUser(user);
console.log(user);
return user;
})
.then(user => {
amendedUser = createUserWithNewName(user);
console.log(user, amendedUser);
})
Update CodeSandbox link: https://codesandbox.io/s/new-react-context-api-tgqi3

React project- chatting app. Need advice on setting interval

below is my main code for my project, which is a simple open chatting room.
This is a chatting client basically, which can only get chatting logs and post chats.
To log in, I request for an authorization key to the server and use that key to start chatting.
The thing that I want to do is to get the chatting logs from the server every 3 seconds.
So, after it retrieves chats every 3 seconds, the page should refresh with new chats.
However, this seems not to be working well for me.
If there is a good React pro who can solve this problem for me, please do.
import React from 'react';
import { withRouter } from 'react-router-dom';
import './Chat.css';
import InfoBar from '../InfoBar/InfoBar';
import Input from '../Input/Input';
import Messages from '../Messages/Messages';
const API_ENDPOINT = 'https://snu-web-random-chat.herokuapp.com';
let INTERVAL_ID;
const Chat = ({ token, setToken }) => {
const [ name, setName ] = React.useState('');
const [ message, setMessage ] = React.useState('');
const [ messages, setMessages ] = React.useState([]);
const [ lastEl, setLastEl ] = React.useState({});
React.useEffect(() => {
if (localStorage.getItem('username')) {
setName(localStorage.getItem('username'));
}
window.addEventListener('scroll', listenToScroll, true);
fetchData();
INTERVAL_ID = setInterval(() => {
fetchData();
}, 3000);
return () => {
window.removeEventListener('scroll', listenToScroll);
clearInterval(INTERVAL_ID);
};
}, []);
const fetchData = () => {
fetch(`${API_ENDPOINT}/chats?createdAtFrom=${lastEl.createdAt || ''}`)
.then((res) => res.json())
.then((msgs) => {
const updatedMsgs = messages.concat(msgs);
setLastEl(msgs[msgs.length - 1]);
setMessages(updatedMsgs);
});
};
const listenToScroll = (e) => {
const div = document.getElementById('messagesContainer');
// console.log(window.scrollY);
// const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
// const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
// const scrolled = winScroll / height;
};
const onLogin = (e) => {
e.preventDefault();
fetch(`${API_ENDPOINT}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `name=${name}`
})
.then((response) => response.json())
.then(({ key }) => {
if (key) {
setToken(true);
localStorage.setItem('__key', key);
localStorage.setItem('username', name);
}
})
.catch((err) => console.error(err));
};
const sendMessage = (e) => {
e.preventDefault();
if (message) {
fetch(`${API_ENDPOINT}/chats`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Key ${localStorage.getItem('__key')}`
},
body: `message=${message}`
})
.then((response) => response.json())
.then((res) => {
const obj = {
createdAt: res.createdAt,
message: res.message,
userName: res.user.name,
_id: res.user._id
};
setMessages([ ...messages, obj ]);
})
.catch((err) => console.error(err));
setMessage('');
}
};
const logout = () => {
localStorage.removeItem('__key');
localStorage.removeItem('username');
setToken(false);
};
const loadMessages = () => {
fetchData();
clearInterval(INTERVAL_ID);
};
return (
<div className="outerContainer">
<div className="innerContainer">
<InfoBar
name={name}
token={token}
handleInput={(e) => setName(e.target.value)}
handleLogin={onLogin}
handleLogout={logout}
/>
<Messages messages={messages} name={name} lastEl={lastEl} loadMore={loadMessages} />
{token && <Input message={message} setMessage={setMessage} sendMessage={sendMessage} />}
</div>
</div>
);
};
export default withRouter(Chat);
I can give u the advice to make a chat app with socket.io, not setInterval.
https://socket.io/get-started/chat

Resources