How to call API when submitting the form? - javascript

I have the API and Login form, separately. Now I want to send my login credentials to API.
i don't know about how to call API and return data. const handleSubmit = (e) => {} inside of this, how to call API?
//login page
const initialFormData = Object.freeze({
username: "",
password: ""
});
export function LoginForm(props) {
const { switchToSignup } = useContext(AccountContext);
const [formData, updateFormData] = React.useState(initialFormData);
const handleChange = (e) => {
updateFormData({
...formData,
//whitespace
[e.target.name]: e.target.value.trim()
});
};
const handleSubmit = (e) => {
e.preventDefault()
console.log(formData);
this.props.history.push('/auth')
};
return (
<BoxContainer className="mobiview">
<FormContainer>
<Input type="text" placeholder="User name" name="username" onChange={handleChange}/>
<Input type="password" placeholder="Password" name="password" onChange={handleChange}/>
</FormContainer>
<SubmitButton type="submit" onClick={handleSubmit}>Signin</SubmitButton>
</BoxContainer>
//API (Its in another folder)
export const authProvider = {
// authentication
login: ({ username, password }) => {
const request = new Request(url + "/auth", {
method: "POST",
body: JSON.stringify({ username, password }),
headers: new Headers({ "Content-Type": "application/json" }),
});
...
...
}
}

Import the auth file, invoke from handleSubmit
import authProvider from "./path-to-file";
const handleSubmit = (e) => {
e.preventDefault()
console.log(formData);
const { username, password } = formData;
authProvider.login({
username,
password
});
};

Related

Sending form data from react client side to node.js server side

I have a login form at the client side (react) that I try to submit and pass the credentials to the login function at the server side (node.js)
when I use postman to send raw json object with the user name and password it works fine, but when I sent it through the client side the req.body contains only this: [[Prototype]]:
Object
what am I doing wrong here?
here is the code of the component that contains the form:
import React from 'react';
import '../signIn/signIn.component.css'
import { Link } from "react-router-dom";
import { useState, useEffect } from "react";
export default function SignIn() {
const [UserName, setUsername] = useState(null);
const [PassWord, setPassWord] = useState(null);
const [FormData, setFormData] = useState({});
useEffect(() => {
setFormData({ UserName: UserName, PassWord: PassWord });
}, []);
const submitFormSignIn = () => {
const testURL = "http://localhost:3100/login";
const myInit = {
method: "POST",
mode: 'no-cors',
body: JSON.stringify(FormData),
headers: {
'Content-Type': 'application/json'
},
};
const myRequest = new Request(testURL, myInit);
fetch(myRequest).then(function (response) {
return response;
}).then(function (response) {
console.log(response);
}).catch(function (e) {
console.log(e);
});
}
return (
<React.Fragment>
<form onSubmit={(e) => { submitFormSignIn(); e.preventDefault(); }}>
<div className="signIn-form-container">
<h1 className="welcome-header">Welcome</h1>
<div className="userName-form-container">
<input className="input-user-name" type="text" name="userName" placeholder='User name'
//should start with an alphabet so. All other characters can be alphabets, numbers or an underscore so.
required
pattern="^[A-Za-z][A-Za-z0-9_]{7,29}$"
minLength={"6"}
maxLength={"20"}
onChange={(e) => setUsername(e.target.value)}
></input>
</div>
<div className="password-form-container">
<input className="input-password" type="password" name="passWord" required
//Minimum eight characters, at least one uppercase letter, one lowercase letter and one number:
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$"
autoComplete="on"
minLength={"9"}
maxLength={"20"}
placeholder='Password'
onChange={(e) => setPassWord(e.target.value)}
></input>
</div>
<div className="forgot-remember-container">
<Link className="userName-forgot-link" to="/userNameRecovery">Forgot user name?</Link>
<Link className="password-forgot-link" to="/passwordRecovery">Forgot password?</Link>
</div>
<div className="form-submit-btn-container">
<button className="form-submit-btn">Sign in</button>
</div>
<div className="sign-up-container">
<a>Don't have an account?</a>
<Link className="signUp-link" to="/register">Sign up</Link>
</div>
<hr></hr>
</div>
</form>
</React.Fragment>
);
}
Your useEffect is fired only once - after initial render, because it's dependency array is empty. It means, you don't set for formData state with proper data.
I see two solutions:
Either fill the dependency array with UserName and PassWord states:
useEffect(() => {
setFormData({ UserName: UserName, PassWord: PassWord });
}, [UserName, PassWord]);
Or - and I would recommend this - easily create your body Object directly from UserName and PassWord states to :
body: JSON.stringify({UserName, PassWord}),
Small underline notice: states are variables, so their name should be camelCase, with lowercase at the beginning. Variables with UpperCase are intended to be React Components.
useEffect in this case absolutely unnecessary, so you have both and submit handler and useEffect that actually make you app rerender several extra times through setState, so Id build this something like that
import React from 'react';
import '../signIn/signIn.component.css'
import { Link } from "react-router-dom";
import { useState } from "react";
export default function SignIn() {
const [username, setUsername] = useState(null);
const [password, setPassword] = useState(null);
const submitFormSignIn = () => {
const testURL = "http://localhost:3100/login";
const myInit = {
method: "POST",
mode: 'no-cors',
body: JSON.stringify({ username, password }),
headers: {
'Content-Type': 'application/json'
},
};
const myRequest = new Request(testURL, myInit);
fetch(myRequest).then(function (response) {
return response;
}).then(function (response) {
console.log(response);
}).catch(function (e) {
console.log(e);
});
}
return (
<React.Fragment>
//same jsx
</React.Fragment>
);
}

JavaScript: cannot catch error from an async function

handleEmailSubmit function doesn't catch any errors even if loginWithEmail function throws an error.
Probably my lack of understanding of async functions.
i want your help. thank you.
Login.tsx
const Login: React.FC = () => {
const [errorMsg, setErrorMsg] = useState<string>('');
const history = useHistory();
const handleEmailSubmit = useCallback(async (e) => {
e.preventDefault();
const { email, password } = e.target.elements;
loginWithEmail(email.value, password.value)
.then(() => {
history.push('/');
})
.catch((error) => {
// this block isn't called!
setErrorMsg(error.message);
});
}, []);
return (
<>
<h2>Login</h2>
<form onSubmit={handleEmailSubmit}>
<InputGroup>
<label htmlFor="email">Email</label>
<TextField
id="email"
name="email"
type="email"
/>
</InputGroup>
<InputGroup>
<label htmlFor="password">Password</label>
<TextField
id="password"
name="password"
type="password"
/>
</InputGroup>
<Button type="submit">
้€ไฟกใ™ใ‚‹
</Button>
</form>
</>
);
}
loginWithEmail definition
import axios from 'axios';
// firebase
import 'firebase/auth';
import firebase from 'firebase/app';
export const loginWithEmail = async (
email: string,
password: string
): Promise<void> => {
app
.auth()
.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
userCredential.user?.getIdToken(true).then((token: string) => {
axios
.get('https://dev.myserver.com/api/v1/users/auth', {
headers: { Authorization: `Bearer ${token}` },
})
.catch((error) => {
app.auth().signOut();
throw error;
});
});
})
.catch((error) => {
console.log(error);
});
};
To catch an error from an async function, you can use try/catch and await the error:
const handleEmailSubmit = useCallback(async (e) => {
e.preventDefault();
const { email, password } = e.target.elements;
try {
await loginWithEmail(email.value, password.value);
history.push('/');
} catch(error) {
const message = await error.message;
setErrorMsg(message);
}
}, []);
You need to throw error in the catch statement.
app
.auth()
.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
userCredential.user?.getIdToken(true).then((token: string) => {
axios
.get('https://dev.myserver.com/api/v1/users/auth', {
headers: { Authorization: `Bearer ${token}` },
})
.catch((error) => {
app.auth().signOut();
throw error;
});
});
})
.catch((error) => {
console.log(error);
throw error; // there could be something wrong here
});

When should I call custom hook not breaking any rules of hooks?

I do have a simple component with form. I want to use useSendEmail hook which returns the server response (success or failure). Where do I call this hook so it doesn't fire on the first render but only after I get my data from the user and save it in the state?
Expected behaviour: hook useSendEmail is called when the email object contains users input and returns status (if sent successfully or not).
I am aware I need to call hook at the top level in the component, but I do I wait for the data from input fields?
Actual behaviour: I am breaking the rule of hooks.
// importing everything I need here
const ContactPage = () => {
const initial = {
from: '',
message: '',
email: '',
};
const [formData, setFormData] = useState(initial);
const [email, setEmail] = useState(null);
const handleChange = ({ name, value }) => {
setFormData({ ...formData, [name]: value });
};
useEffect(() => {
if (email === null) return;
const response = useSendEmail(email);
}, [email]);
const handleSubmit = (e) => {
e.preventDefault();
setEmail(formData);
};
return (
<DefaultLayout title="Contact">
<StyledContainer>
<form className="contact_form" onSubmit={(e) => handleSubmit(e)}>
<input
name="from"
type="text"
value={formData.from}
onChange={(e) => handleChange(e.target)}
placeholder="Your full name"
/>
<textarea
name="message"
value={formData.message}
onChange={(e) => handleChange(e.target)}
placeholder="Your Message"
/>
<input
name="email"
type="email"
value={formData.email}
onChange={(e) => handleChange(e.target)}
placeholder="Your e-mail"
/>
<button type="submit">SUBMIT</button>
</form>
</StyledContainer>
</DefaultLayout>
);
};
export default ContactPage;
EDIT:
this is how my hook looks like after refactoring with your suggestions. I am now importing the hook and the method in the top level component and everything seems to work perfectly.
import { useState } from 'react';
import emailjs from 'emailjs-com';
import { userID, templateID, serviceID } from '../data/account';
const useSendEmail = (email) => {
const [response, setResponse] = useState(null);
const successMsg = 'Your message has been successfully sent';
const errorMsg = 'Your message has not been sent. Try again.';
const sendEmail = async () => emailjs
.send(serviceID, templateID, email, userID)
.then(
(res) => {
if (res.status === 200) {
setResponse(successMsg);
}
if (res.status !== 200) {
setResponse(errorMsg);
}
},
(err) => {
console.log(err);
},
);
return { response, sendEmail }
};
export default useSendEmail;

fetch send the data but doesn't go in then, React

I have problem with fetch. The data have been send, but doesn't do in then or catch, so I don't get if response is send ot not, only when I go in database. That is the code:
import React, { useState } from 'react'
// import styles from './index.module.css'
import Input from '../input'
import SignUpButton from '../sign-up-button'
const SignUpForm = () => {
const [username, setUsername] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
let data = { username: username, email: email, password: password }
const headers = new Headers()
headers.append('Content-Type', 'application/json')
const options = {
method: 'POST',
headers: headers,
mode: 'cors',
cache: 'default',
body: JSON.stringify(data)
}
const request = new Request(`http://localhost:5000/user/sign-up`, options)
fetch(request)
.then(res => {
setUsername('')
setEmail('')
setPassword('')
})
.catch(e => {
console.log(e)
})
}
return (
<form onSubmit={handleSubmit}>
<Input
onChange={e => setUsername(e.target.value)}
label='Username:'
name='username'
placeholder='marolio'
value={username}
/>
<Input
onChange={e => setEmail(e.target.value)}
label='Email:'
name='email'
placeholder='marolio#yahoo.com'
value={email}
/>
<Input
onChange={e => setPassword(e.target.value)}
label='Password:'
name='password'
value={password}
/>
<SignUpButton
text='CREATE ACCOUNT'
btnStyle='submit'
/>
</form>
)
}
export default SignUpForm
Same structire worked for me in other project, so maybe something is changed but I don't know it. Every help will be useful. Thanks!
I made this change and it work, but I'm still curious why then and catch doesn't worked.
const response = fetch(request)
if(response){
setUsername('')
setEmail('')
setPassword('')
} else{
console.log('error')
}

Submit a form with data using a custom React hook

I'm having trouble figuring this out. I want to create a hook that's called to submit a form using fetch.
This is what I have right now. The component holding the form:
const MyForm = (): ReactElement => {
const [status, data] = useSubmitForm('https://myurl-me/', someData);
return <>
<div className='Feedback-form'>
<div className='body'>
<form>
<input type='text' name='username' placeholder='name' required />
<input type='email' name='email' placeholder='email' required />
<button className='submit-feedback-button' type='button'>Send feedback</button>
</form>
</div>
</div>
</>
}
The custom hook:
import { useState, useEffect } from 'react';
const useSubmitForm = (url: string, data: URLSearchParams): [string, []] => {
const [status, setStatus] = useState<string>('idle');
const [responseData, setData] = useState<[]>([]);
useEffect(() => {
if (!url) return;
const fetchData = async () => {
setStatus('fetching');
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'text/html',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data
});
const data = await response.json();
setData(data);
setStatus('fetched');
};
fetchData();
}, [url]);
return [status, responseData];
};
export default useSubmitForm;
My problem is that I think this hook is being called right away. How do I make this hook and call it in such a way that it's only called when the form is submitted and all the data I need to send in the request body is there to be included?
You are correct, the effect runs once when the component mounts and since url is truthy, it skips the early return and invokes fetchData.
How do I make this hook and call it in such a way that it's only
called when the form is submitted and all the data I need to send in
the request body is there to be included?
You need to also return a function for the component to invoke and pass along the form field values. I think you've a couple basic options.
Convert the form fields to be controlled inputs and store the field state in the component and invoke a "fetch" function returned from the useSubmitForm hook.
Return an onSubmit handler from the useSubmitForm to attach to your form element. The onSubmit handler would need to know what fields to access from the onSubmit event though, so passing an array of field names to the hook (i.e. a "config") makes sense.
Solution 1 - Use controlled inputs and returned fetch function
Unwrap the fetchData function from the useEffect hook and add a form field data parameter to it. Since fetch and response.json() can both throw errors/rejections you should surround this block in a try/catch. Return the custom fetchData function for the form to invoke.
useSubmitForm
const useSubmitForm = (
url: string,
data: URLSearchParams
): [function, string, []] => {
const [status, setStatus] = useState<string>("idle");
const [responseData, setData] = useState<[]>([]);
const fetchData = async (formData) => {
setStatus("fetching");
try {
const response = await fetch(url, {
method: "POST",
headers: {
Accept: "text/html",
"Content-Type": "application/x-www-form-urlencoded"
},
body: JSON.stringify(formData)
});
const data = await response.json();
setData(data);
setStatus("fetched");
} catch (err) {
setData(err);
setStatus("failed");
}
};
return [fetchData, status, responseData];
};
MyForm
const MyForm = (): ReactElement => {
const [fields, setFields] = useState({ // <-- create field state
email: '',
username: '',
});
const [fetchData, status, data] = useSubmitForm(
"https://myurl-me/",
someData
);
useEffect(() => {
// handle successful/failed fetch status and data/error
}, [status, data]);
const changeHandler = (e) => {
const { name, value } = e.target;
setFields((fields) => ({
...fields,
[name]: value
}));
};
const submitHandler = (e) => {
e.preventDefault();
fetchData(fields); // <-- invoke hook fetchData function
};
return (
<div className="Feedback-form">
<div className="body">
<form onSubmit={submitHandler}> // <-- attach submit handler
<input
type="text"
name="username"
placeholder="name"
onChange={changeHandler} // <-- attach change handler
value={fields.username} // <-- pass state
/>
<input
type="email"
name="email"
placeholder="email"
onChange={changeHandler} // <-- attach change handler
value={fields.email} // <-- attach state
/>
<button className="submit-feedback-button" type="submit">
Send feedback
</button>
</form>
</div>
</div>
);
};
Solution 2 - Return an onSubmit handler and pass an array of fields to the useSubmitForm
useSubmitForm
const useSubmitForm = (
url: string,
data: URLSearchParams,
fields: string[],
): [function, string, []] => {
const [status, setStatus] = useState<string>("idle");
const [responseData, setData] = useState<[]>([]);
const fetchData = async (formData) => {
setStatus("fetching");
try {
const response = await fetch(url, {
method: "POST",
headers: {
Accept: "text/html",
"Content-Type": "application/x-www-form-urlencoded"
},
body: JSON.stringify(formData)
});
const data = await response.json();
setData(data);
setStatus("fetched");
} catch (err) {
setData(err);
setStatus("failed");
}
};
const onSubmit = e => {
e.preventDefault();
const formData = fields.reduce((formData, field) => ({
...formData,
[field]: e.target[field].value,
}), {});
fetchData(formData);
}
return [onSubmit, status, responseData];
};
MyForm
const MyForm = (): ReactElement => {
const [onSubmit, status, data] = useSubmitForm(
"https://myurl-me/",
someData,
['email', 'username'] // <-- pass field array
);
useEffect(() => {
// handle successful/failed fetch status and data/error
}, [status, data]);
return (
<div className="Feedback-form">
<div className="body">
<form onSubmit={onSubmit}> // <-- attach submit handler
<input
type="text"
name="username"
placeholder="name"
/>
<input
type="email"
name="email"
placeholder="email"
/>
<button className="submit-feedback-button" type="submit">
Send feedback
</button>
</form>
</div>
</div>
);
};
In my opinion the second solution is the cleaner solution and requires less on consuming components to use.

Categories

Resources