Edit form that is pre-filled with API data - React - mysql

I am building a form that is pre-populated by the results of an axios get request to a nodejs API that returns an array (stored in MySQL). I asked this question yesterday, implemented a solution that was provided and it worked. The next day I tested it thoroughly and it turns out that on submission only the edited fields were passed but not the values from the unedited fields.
I can get the data to map onto the form, but i cannot edit the form. The idea is for it to be an "edit user" form. I suspect the issue is in the onChange portion of the input field.
The form is accessed from a table that is also mapped with the results of a get request. Upon clicking the edit button, the userID from the table row is passed to the edit form through useNavigate and useLocation (I can add this portion of code if needed).
Backend
Here is the API controller that queries the MySQL database and then sends to the frontend:
export const edit = async (req, res) => {
db.query(
"SELECT * FROM userIndex WHERE userID = ?",
[req.params.userID],
(err, rows) => {
if (!err) {
res.send(rows);
} else {
console.log(err).res.send({ alert: err });
}
}
);
};
Here's an example result of the query above:
[
{
"userID": 143,
"firstName": "Kenan",
"lastName": "Erasmus",
"role": "student",
"email": "kenan#gmail.com",
"password": "$2b$12$v3s0D6cNkGwM3/IWXPdv..TRfRZLmDNuZBfrWlUCt4vKnyRi75jWe",
"createdAt": "06/10/2022, 13:56:51",
"updatedAt": "07/10/2022, 19:45:46",
"lastLogin": null
}
]
Frontend
Here is the portion of code that performs the request to the API:
useEffect(() => {
const config = {
headers: { "x-auth-token": token },
};
const fetchData = async () => {
const results = await api.get("/users/edituser/" + userID, config);
setRows(results.data);
setFirstName(rows.firstName)
};
fetchData();
}, [setRows, userID, token]);
State for "rows" (set on API response):
const [rows, setRows] = useState([]);
const [firstName, setFirstName] = useState("");
And finally, an example input field:
<input
type="text"
className="form-control"
id="inputEmail4"
placeholder="First Name"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
></input>
I have only included the "firstName" input as an example. In total, this form has about 6 fields.
I would greatly appreciate any assistance with this. Let me know if I can supply any more code.

Found a workaround that seems quite logical to me. I initialised new blank states for each of the input fields
const [firstName, setFirstName] = useState("");
Then mapped the form with "rows" and set each field value to its correspond state (as seen in the useEffect below)
Frontend useEffect:
useEffect(() => {
const config = {
headers: { "x-auth-token": token },
};
const fetchData = async () => {
const results = await api.get("/users/edituser/" + userID, config);
setRows(results.data);
setFirstName(results.data[0].firstName);
setLastName(results.data[0].lastName);
setEmail(results.data[0].email);
setPassword(results.data[0].password);
setRole(results.data[0].role);
};
fetchData();
}, [setRows, userID, token]);
Example input field:
<input
type="text"
className="form-control"
id="inputEmail4"
placeholder="First Name"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
></input>
By doing it this way, the form maps through "rows", and then each input field immediately gets a new state once typing occurs.
I know it's ugly, but it is effective. I'm new to coding so I don't know all the ins and outs of React yet - but this solution works for me. Hope this helps anyone looking for a solution to the same issue!

Related

I wanted to get users from an array of names but it throws an error

Create an async function getUsers(names), that gets an array of GitHub logins, fetches the users from GitHub and returns an array of GitHub users.
The GitHub url with user information for the given USERNAME is: https://api.github.com/users/USERNAME.
There’s a test example in the sandbox.
Important details:
1.There should be one fetch request per user.
2.Requests shouldn’t wait for each other. So that the data arrives as soon as possible.
3.If any request fails, or if there’s no such user, the function should return null in the resulting array.
Input:array;
output:array;
TypeError: r.json is not a function
async function getUsers(names) {
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));//gets users
let users = [];//Final answer
await Promise.allSettled(requests)
.then(responses => new Promise(function(resolve) {// returrn correct users promise
let corrects = [];
responses.forEach((result) => {
if (result.value.ok) { //check statuse 200-299
corrects.push(result);
} else {
users.push(result); // else add to Finell answer null
}
})
resolve(corrects); //return users with 200-299 statuse
}))
.then(corrects => Promise.all(corrects.map(r => r.json()))) //processing
.then(results => results.forEach(result => users.push(result))); //add to finel answer correct requests
return users;
}
//Input:array;
//output:array;
//TypeError: r.json is not a function
There's a number of things slightly wrong with your code, but I think the main issue is that you're pushing the results of allSettled into 'corrects' but, you want to push the .value instead.
You also don't actually do anything with corrects and only return failed requests.
But here's a version that cleans it all up. I'm assuming you want to ignore failed requests, but not sure, because it's hard to tell from your code:
async function getUsers(names) {
const requests = names.map(name => fetch(`https://api.github.com/users/${name}`));//gets users
const results = await Promise.allSettled(requests);
const successResponses = results
.filter(result => {
// Filter out rejected promises and error responses.
// I think this is what you want but not sure?
if (result.status!=='fulfilled' || !result.value.ok) return false;
});
return Promise.all(successResponses.map(response => response.json()));
}
Promise.allSettled is a very special-purpose function and you will not need it in most cases. There are other pain points like the explicit promise constructor anti-pattern. Instead decompose the problem into smaller, simple parts -
getUser(name) takes a single name and returns a user object or null
getUsers(names) takes a list of names and maps getUser over each
async function getUser(name) {
try {
const res = await fetch(`https://api.github.com/users/${name}`)
return res.ok ? res.json() : null
}
catch (err) {
return null
}
}
function getUsers(names) {
return Promise.all(names.map(getUser))
}
getUsers(["ivg", "glennsl", "jeffsco", "nosuchuser111"]).then(console.log, console.error)
.as-console-wrapper { min-height: 100%; top: 0; }
[
{
"login": "ivg",
"id": 2336698,
"node_id": "MDQ6VXNlcjIzMzY2OTg=",
...
},
{
"login": "glennsl",
"id": 5207036,
"node_id": "MDQ6VXNlcjUyMDcwMzY=",
...
},
{
"login": "jeffsco",
"id": 4043178,
"node_id": "MDQ6VXNlcjQwNDMxNzg=",
...
},
null // user not found
]

How can I read JSON data from a Sequelize search?

I am developing a handlebars app,
router.get('/findBehavior/:student_id', async (req, res) => {
console.log("---> req.params.student_id :" + JSON.stringify(req.params.student_id));
const student = await Student.findByPk(req.params.student_id,{
include: Behavior,
});
console.log("---> student :" + JSON.stringify(student));
res.render('profile', {student, session: req.session});
});
but I have trouble reading this json data that comes into the profile.handlebars:
{
"student_id": 1,
"student_name": "Martina Hodson",
"student_grade": 9,
"Behaviors": [
{
"behavior_id": 1,
"behavior_name": "No Problems",
"StudentBehavior": {
"student_id": 1,
"behavior_id": 1
}
}
]
}
I am trying this code, but it is not working...
{{student.Behaviors.[0].['behavior_id']}}
I get a response of
[object SequelizeInstance:Behavior]
How can I get the student's name and the student's behaviors?
The problem was not the way I was trying to read the JSON.
It was that the object that comes in response of the search needs to be 'cleaned' before being passed to the handlebars page.
I used this code to make the object plain:
const student = dbStudentData.get({plain: true});
Yo can see how it appears surrounded by the rest of the code:
router.get('/findBehavior/:student_id', async (req, res) => {
console.log("---> req.params.student_id :" + JSON.stringify(req.params.student_id));
const dbStudentData = await Student.findByPk(req.params.student_id, {
include: Behavior,
});
// HERE --------------------------------------------------------
const student = dbStudentData.get({plain: true});
console.log("---> student :" + JSON.stringify(student));
res.render('profile', {student, session: req.session});
});
Since it was only one row of data, It wasn't needed a map function to make plain whole set of data when there is more than one row.

Socket.io emitting undefined variable React

I've currently got a very simple app that sends and receives data to a server via a websocket connection. Essentially, the app contains two text boxes and each time a user clicks on either of the textboxes to focus or click away, a notification is sent to the server and then the latter broadcasts that message to all connected instances.
This is my code below:
App.js (client)
import React, { useState, useEffect } from 'react'
import io from 'socket.io-client'
import TextField from '#material-ui/core/TextField'
import './App.css'
import 'carbon-components/css/carbon-components.min.css';
import { TextArea } from 'carbon-components-react';
const socket = io.connect('http://localhost:4000')
function App() {
const [myUser, setMyUser] = useState(null)
const handleButtonClick = (e) => {
e.preventDefault();
setMyUser(e.target.value)
}
useEffect(() => {
socket.on("message", ({ user, id, focus }) => {
console.log(user, "clicked on", id, "with focus", focus)
})
}, [])
function setFocusTrue(id) {
const focus = true
console.log("emitting: ", myUser, id, focus)
socket.emit('message', { myUser, id, focus })
}
function setFocusFalse(id) {
const focus = false
console.log("emitting: ", myUser, id, focus)
socket.emit('message', { myUser, id, focus })
}
const Main = () => {
return (
<div>
<h1>Hello {myUser}</h1>
<TextArea
cols={50}
helperText="Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)"
id="text1"
invalidText="Invalid error message."
labelText="Text area label"
placeholder="Placeholder text"
rows={4}
onFocus={() => setFocusTrue('text1')}
onBlur={() => setFocusFalse('text1')}
/>
<TextArea
cols={50}
helperText="Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)"
id="text2"
invalidText="Invalid error message."
labelText="Text area label"
placeholder="Placeholder text"
rows={4}
onFocus={() => setFocusTrue('text2')}
onBlur={() => setFocusFalse('text2')}
/>
</div>
)
}
return (
<div>
{myUser === null ?
[
<div>
<button onClick={handleButtonClick} value="user1">User 1</button>
<button onClick={handleButtonClick} value="user2">User 2</button>
</div>
]
: <Main />
}
</div>
)
}
export default App
index.js (server)
const app = require('express')()
const http = require('http').createServer(app)
const io = require('socket.io')(http)
io.on('connection', socket => {
socket.on('message', ({ user, id, focus }) => {
console.log('received user', user, 'id', id, 'focus', focus)
io.emit('message', { user, id, focus })
})
})
http.listen(4000, function() {
console.log('listening on port 4000')
})
One thing to note is that I'm testing this with two different users (running on two incognito browsers), hence the conditional rendering for each user. For this example, when either user clicks on or away from a textbox, I can see that the textbox id and focus are both valid, but for some reason, the user is undefined. I can see that the instance emits the variables as expected, but they are not being received in the server correctly.
In this particular case, user 1 clicks on the first textbox (thus rendering the focus to true). Each of the variables to emit to the server are set (as can be seen by the console log). The server receives the focus and id variables, but for an odd reason, the user is undefined, even though it exists. Finally, the broadcasted message is sent to both instances but as mentioned, id and focus exist, but user is undefined. See below screenshots.
Client side
Server side console
Instead of emitting the object like socket.emit('message', { myUser, id, focus }), I instead provided the user object as such socket.emit('message', { user: myUser, id, focus }) which seemed to work fine.

Firebase Updating User Data With Custom Fields After Creating User

I want to update the newly created User's data. The returned JSON is:
{
"user":{
"uid":"test123",
"displayName":null,
"photoURL":null,
"email":"test12#test.com",
"emailVerified":false,
"phoneNumber":null,
"isAnonymous":false,
"tenantId":null,
"providerData":[
{
"uid":"test12#test.com",
"displayName":null,
"photoURL":null,
"email":"test12#test.com",
"phoneNumber":null,
"providerId":"password"
}
],
"apiKey":"test123",
"appName":"[DEFAULT]",
"authDomain":"test123.firebaseapp.com",
"stsTokenManager":{
"apiKey":"test123",
"refreshToken":"test123",
"accessToken":"test123",
"expirationTime":1571238989357
},
"redirectEventId":null,
"lastLoginAt":"1571235389108",
"createdAt":"1571235389108"
},
"credential":null,
"additionalUserInfo":{
"providerId":"password",
"isNewUser":true
},
"operationType":"signIn"
}
This is my callout and update:
createUser = async (userData) => {
return await firebase.auth().createUserWithEmailAndPassword(userData.get('userName'), userData.get('password'))
.then((authData) => {
firebase.database().ref('users/' + authData.user.uid + '/').set({
fullName: userData.get('fullName'),
pictures: userData.get('pictures'),
phoneNumber: userData.get('phoneNumber')
});
})
};
Is it possible to add to the User table custom fields?
A few things are happening. It appears that userData can not be seen in the .then statement. So to solve this I attempted to pass in the userData JSON as a param. This did not work. I then broke out each value out of userData, saved it into a const and passed that value. This did not work.
I can see that userData has values in it before the .then statement. I am able to successfully create a new user with the right userName and password. This means to me either:
A - I am not passing the userData JSON correctly or
B - I am not allowed to pass data to firebase like I am doing
My end goal is to sign up a user and then take all of the data they input from a registration form (aka userData) and update the user table with it.
Articles I am using are:
https://firebase.google.com/docs/auth/web/manage-users
https://medium.com/mindorks/firebase-realtime-database-with-react-native-5f357c6ee13b
Main class that calls the createUser function:
const signUp = (dispatch) => {
return async (userData)=>{
try{
const response = await config.createUser(userData);
console.log('sign up resonse1: ' + response); //coming back as undefined
//todo:: figure out how to parse out the apikey out of response
await AsyncStorage.setItem('token', '123mockToken');
dispatch({type: 'sign_up', payload: '123mockToken'});
navigate('mainFlow');
} catch(e){
dispatch({type: 'add_error', payload: '' + e}); //we call dispatch anytime we want to update our state
}
}
};
I understand that the parameter userData holds all the data you want to use for creating the user ("all of the data they input from a registration form").
The following should work:
createUser = async userData => {
try {
const userCredential = await firebase
.auth()
.createUserWithEmailAndPassword(
userData.get('userName'),
userData.get('password')
);
const userId = userCredential.user.uid;
await firebase
.database()
.ref('users/' + userId + '/')
.set({
fullName: userData.get('fullName'),
pictures: userData.get('pictures'),
phoneNumber: userData.get('phoneNumber')
});
return userId; //As per your comment below
} catch (error) {
return error;
}
};
The createUserWithEmailAndPassword() method returns a UserCredential which contains a User.

fetching data with react hook returns undefined on nested obj properties

Im trying to display data that has been fetched. but i cannot seem to display nested objects properties in react. Any ideas? if i log the data i first get a undefined, then the correct data.
my guess is that i need to wait for the data to be loaded then display it. but it does work for the title that is not in a nested obj.
function SingleBeneficiary({ match }) {
const [data, setData] = useState({ data: []});
const id = match.params.id
useEffect(() => {
async function fetchData() {
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
setData(jsonData)
}
fetchData();
}, [])
return (
{data.title} // works
{data.address.careOf} // dont work
The data
{
"title":"myTitle",
"address":{
"careOf": "my adress"
}
}
Can you try like this?
I set initial data to null, and in return I check if it is not null.
If address can be null, additional null check is required.
function SingleBeneficiary({ match }) {
const [data, setData] = useState(null);
const id = match.params.id
useEffect(() => {
async function fetchData() {
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
setData(jsonData)
}
fetchData();
}, [])
return (
<div>
{data && (
<div>
<p>data.title</p>
<p>data.address.careOf</p>
</div>
)}
</div>
);
}
You should check if address has careOf property before using it because first time data will be undefined and in second render it will have the data after the api call.
{data.address && data.address.careOf}
For anyone who is having a similar issue(i.e. fetching data via api and only the first time it runs, it will show the data as undefined but after manual refreshing, it works fine), here is a quick and sketchy addition you might consider alongside with 1. "Inline If with Logical && Operator" method and 2. using useState for checking if the api loading is over. With those three, mine worked.
Try fetching the desired data in the previous page of your app; in this case, add the following lines in any page you'll see before "SingleBeneficiary".
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
Maybe it has to do with npm cache, but not really sure what's going on.
replace
return (
{data.title}
{data.address.careOf}
)
with
return (
{data?.title}
{data?.address?.careOf}
)