I'm trying to validate my email and password TextField(s) for a user logging in. I'm able to catch errors via my handleSubmit function, but unsure of how to implement those errors into the MaterialUI error and helperText fields.
Note, I'm using both material-ui and react-bootstrap, that's why they're mixed.
Login.js - where the email and password TextField(s) are
import React, { Component } from 'react';
import firebase from '../firebase';
import { FiLogIn } from 'react-icons/fi';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField'
import Form from 'react-bootstrap/Form';
import Col from 'react-bootstrap/Col';
export class Login extends Component {
state = {
email : "",
password : ""
};
handleChange = (e) => {
const { id, value } = e.target
this.setState(prevState => ({
...prevState,
[id] : value
}))
};
handleSubmit = (e) => {
e.preventDefault();
const { email, password } = this.state;
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((user) => {
// User is signed in
})
.catch((error) => {
// Error
});
};
render() {
const { email, password } = this.state;
return (
<>
<Form className="sign-in-form">
<Form.Row className="align-items-center">
<Col xs="auto">
<Form.Group controlId="email">
<Form.Label srOnly>Email Address</Form.Label>
<TextField
id="email"
label="Email"
type="email"
variant="outlined"
aria-describedby="emailHelp"
placeholder="Enter email"
value={email}
onChange={this.handleChange}
/>
</Form.Group>
</Col>
<Col xs="auto">
<Form.Group controlId="password">
<Form.Label srOnly>Password</Form.Label>
<TextField
id="password"
label="Password"
variant="outlined"
type="password"
placeholder="Enter password"
value={password}
onChange={this.handleChange}
/>
</Form.Group>
</Col>
</Form.Row>
</Form>
<Button variant="contained" color="primary" className="login" type="submit" onClick={this.handleSubmit}><FiLogIn className="loginIcon" /> Login</Button>
</>
)
}
}
handleSubmit Function - where firebase validation errors are caught
handleSubmit = (e) => {
e.preventDefault();
const { email, password } = this.state;
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((user) => {
// User is signed in
})
.catch((error) => {
// Error
});
};
Let me know of what I can do here, I'm relatively new with React and always looking to learn new things.
Try this approach:
add an error state to your component:
state = {
email : "",
password : "",
error : false,
errMsg : ""
};
then change it when error is thrown from the firebase auth action inside handleSubmit:
.catch((error) => {
this.state = {error : true, errMsg: error.msg};
});
last, add a conditional TextField to show the error message:
{error && <TextField
error
id="yourErrorId"
helperText=this.state.errMsg
variant="outlined"
/>}
Make an state for error:
state = {
email : "",
password : "",
error:"",
};
Change it on catching error:
.catch((error) => {
this.setState({error: error.response.data}) // change it to your error response
});
And your input should be something like this:
<FormControl error={error}>
<InputLabel htmlFor="email">Email</InputLabel>
<Input
id="email"
value={email}
onChange={this.handleChange}
aria-describedby="email"
/>
<FormHelperText id="email"> {error ? error : "Enter your email address"}</FormHelperText>
</FormControl>
Remember to clear error state with handleChange.
Related
Here I have a login form that I created in react js. The API I'm using gives a response as "Successful Login" or "Authentication Failed. Unable to login" depending on whether the login credentials match or not. In the login form I'm using a react hook and axios.post to send the "name" and "password" to the API. How can I also print the response I'm getting back from the API?
Here is the Login.js component:
import React, { Component } from "react";
import { useState, useEffect } from "react";
import axios from "axios";
import { Button, TextField } from "#mui/material";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
name: "",
password: "",
};
}
changeHandler = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
submitHandler = (e) => {
e.preventDefault();
console.log(this.state);
axios
.post("http://localhost:8080/users/login", this.state)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
};
render() {
const { name, password } = this.state;
return (
<div>
<h1>Login Page</h1>
<form onSubmit={this.submitHandler}>
<TextField
name="name"
label="Enter Username"
color="secondary"
focused
size="small"
variant="outlined"
onChange={this.changeHandler}
id="name"
value={name}
type="text"
placeholder="Username"
className="form-control"
/>
<p />
<TextField
name="password"
label="Enter Password"
color="secondary"
focused
size="small"
variant="outlined"
onChange={this.changeHandler}
id="password"
value={password}
type="text"
placeholder="Password"
className="form-control"
/>
<p />
<Button type="submit" variant="contained">
Login
</Button>
</form>
</div>
);
}
}
export default Login;
And here is what the console shows:
{name: 'Mike', password: 'password1234'}
{data: 'Authentication Failed. Unable to login', status: 200, statusText: '', headers: AxiosHeaders, config: {…}, …}
{name: 'Mike', password: 'Pass1234'}
{data: 'Successful Login', status: 200, statusText: '', headers: AxiosHeaders, config: {…}, …}
Can I use another react hook to fetch the data? The API uses POST method so I'm not sure how to do that.
here is a very basic example of login page using hooks. In the login function, you should call the API you want and use the setResponse to display the response on the screen
const [Name, setName] = useState("");
const [Pass, setPass] = useState("");
const [Response, setResponse] = useState("");
const userChange = (event) => {
setName(event.target.value);
};
const passChange = (event) => {
setPass(event.target.value);
};
const login = () => {
// login using Name and Pass
setResponse("server response")
}
return (
<ThemeComponent>
<TextField label={"user"} onchange={userChange} />
<TextField label={"pass"} onchange={passChange} />
{Response}
<Button onClick={login} text="LOGIN">LOGIN</Button>
</ThemeComponent>
)
I wasn't sure how to phrase the title but basically, I am following a tutorial to create a login/registration and I am currently trying to display a message indicating whether the registration attempt was successful or not.
Here is my Register.js
import React, { useState } from 'react';
import { Form, Button } from 'react-bootstrap';
import axios from "axios";
export default function Register() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [register, setRegister] = useState(false);
const handleSubmit = (e) => {
// prevent the form from refreshing the whole page
e.preventDefault();
//set configuration
const configuration = {
method: "post",
url: "https://nodejs-mongodb-auth-app-learn.herokuapp.com/register",
data: {
email,
password,
},
};
// API call
axios(configuration)
.then((result) => {
console.log(result);
setRegister=(true);
})
.catch((error) => {
error= new Error;
})
};
return (
<>
<h2>Register</h2>
<Form onSubmit={(e)=>handleSubmit(e)}>
{/* email */}
<Form.Group controlId="formBasicEmail">
<Form.Label>Email Address</Form.Label>
<Form.Control
type="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email"
/>
</Form.Group>
{/* password */}
<Form.Group controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter password"
/>
</Form.Group>
{/* submit button */}
<Button
variant="primary"
type="submit"
onClick={(e)=>handleSubmit(e)}
>
Register
</Button>
{/* display success message */}
{register ? (
<p className="text-success">You Are Registered Successfully</p>
) : (
<p className="text-danger">You Are Not Registered</p>
)}
</Form>
</>
)
};
The successful registration will log on the console, but either setRegister is not updating register to true, or my conditional statement is incorrect in some way?
It always shows "You Are Not Registered".
The correct way to ser an state using useState hook is:
e.g
const [register, setRegister] = useState(false);
setRegister(true)
I'm trying to create a login form using formik. I'm confused as to how to fire handleSubmit function to call the login api for a user to login. I kept calling handleSubmit inside onSubmit but it doesn't recognize the values and handleSubmit inside the onSubmit method in line 10 and 11 in my codesandbox in the ValidatedLoginForm.js file. Where do I exactly call the handleSubmit and let the user log in to my website?
my codesandbox
my code looks something like this:
import React, { useState } from "react";
import { Formik } from "formik";
import TextField from "#material-ui/core/TextField";
import * as Yup from "yup";
const ValidatedLoginForm = props => (
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={values => {
const handleSubmit = async event => {
event.preventDefault();
var body = {
password: password,
email: email
};
console.log(body);
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/authenticate";
try {
const response = await fetch(url, options);
const text = await response.text();
if (text === "redirect") {
props.history.push(`/editor`);
} else if (text === "verifyemail") {
props.history.push(`/verifyOtp/${this.state.email}`);
} else {
console.log("login failed");
window.alert("login failed");
}
} catch (error) {
console.error(error);
}
};
}}
//********Using Yup for validation********/
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required("Required"),
password: Yup.string()
.required("No password provided.")
.min(8, "Password is too short - should be 8 chars minimum.")
.matches(/(?=.*[0-9])/, "Password must contain a number.")
})}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<>
<form onSubmit={handleSubmit} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
value={values.email}
label="Email Address"
name="email"
autoComplete="email"
autoFocus
onChange={handleChange}
onBlur={handleBlur}
className={errors.email && touched.email && "error"}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
value={values.password}
label="Password"
type="password"
id="password"
onBlur={handleBlur}
autoComplete="current-password"
className={errors.password && touched.password && "error"}
onChange={handleChange}
/>
{errors.password && touched.password && (
<div className="input-feedback">{errors.password}</div>
)}
<button type="submit" disabled={isSubmitting}>
Login
</button>
</form>
</>
);
}}
</Formik>
);
export default ValidatedLoginForm;
You're currently creating a new function in your onSubmit code that never gets called. The function values => { ... } is called when the form is submitted, but in that function you create handleSubmit and never call it.
If you move the creation of handleSubmit a bit up it all gets easier to read. This will become something like
import React, { useState } from "react";
import { Formik } from "formik";
import TextField from "#material-ui/core/TextField";
import * as EmailValidator from "email-validator";
import * as Yup from "yup";
const ValidatedLoginForm = props => {
// The function that handles the logic when submitting the form
const handleSubmit = async values => {
// This function received the values from the form
// The line below extract the two fields from the values object.
const { email, password } = values;
var body = {
password: password,
email: email
};
console.log(body);
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/authenticate";
try {
const response = await fetch(url, options);
const text = await response.text();
if (text === "redirect") {
props.history.push(`/editor`);
} else if (text === "verifyemail") {
props.history.push(`/verifyOtp/${this.state.email}`);
} else {
console.log("login failed");
window.alert("login failed");
}
} catch (error) {
console.error(error);
}
};
// Returning the part that should be rendered
// Just set handleSubmit as the handler for the onSubmit call.
return (
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={handleSubmit}
//********Using Yup for validation********/
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required("Required"),
password: Yup.string()
.required("No password provided.")
.min(8, "Password is too short - should be 8 chars minimum.")
.matches(/(?=.*[0-9])/, "Password must contain a number.")
})}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<>
<form onSubmit={handleSubmit} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
value={values.email}
label="Email Address"
name="email"
autoComplete="email"
autoFocus
onChange={handleChange}
onBlur={handleBlur}
className={errors.email && touched.email && "error"}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
value={values.password}
label="Password"
type="password"
id="password"
onBlur={handleBlur}
autoComplete="current-password"
className={errors.password && touched.password && "error"}
onChange={handleChange}
/>
{errors.password && touched.password && (
<div className="input-feedback">{errors.password}</div>
)}
<button type="submit" disabled={isSubmitting}>
Login
</button>
</form>
</>
);
}}
</Formik>
);
};
export default ValidatedLoginForm;
I would also move the validationSchema out of your component. Makes it easier to read/understand and it doesn't have to be recreated every time.
In my app I use React Bootstrap and Formik. I want the bootstrap to show that field is invalid (for example is not an email but should be) after I press the submit button. And then when I start typing new values to fields it should disappear. In the tutorial I used I only found the way to show that field is invalid only at the same moment the user is typing the values?
How to do that? How to set isInvalid to show errors only after submit using Formik?
Here is my current code
import * as yup from "yup";
import React from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import {Formik} from "formik";
import {loginActions} from "../_actions/loginActions";
import {connect} from "react-redux";
import {loginService} from "../_services";
const schema = yup.object().shape({
username: yup.string().email("Login musi być w formie e-mail").required("Wypełnij pole login"),
password: yup.string().required("Wypełnij pole hasło")
});
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
const {username, password} = e;
if (username && password) {
loginService
.login(username, password)
.then(
success => {
const data = success.data;
if (success.status === 200 && data.success === true) {
return {...data.user, password: password};
} else if (success.status === 400) {
window.location.reload();
}
const error = (!data.success && "Wrong credentials") || success.statusText;
return Promise.reject(error);
}
)
.then(auth => {
this.props.login(auth)
})
}
}
render() {
return (
<Formik
validationSchema={schema}
onSubmit={e => this.handleSubmit(e)}
initialValues={{username: '', password: ''}}>
{
formProps => (
<Form name='form' onSubmit={formProps.handleSubmit}>
<Form.Group noValidate controlId="loginForm.username">
<Form.Label>Adres e-mail</Form.Label>
<Form.Control
type="text"
name="username"
value={formProps.values.username}
onChange={formProps.handleChange}
isInvalid={!!formProps.errors.username}
/>
<Form.Control.Feedback type="invalid">
{formProps.errors.username}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="loginForm.password">
<Form.Label>Hasło</Form.Label>
<Form.Control
type="password"
name="password"
value={formProps.values.password}
onChange={formProps.handleChange}
>
</Form.Control>
<Form.Control.Feedback type="invalid">
{formProps.errors.password}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="loginForm.loginBtn">
<Button variant="primary" type="submit">
Zaloguj się
</Button>
{formProps.isSubmitting &&
(
<img
src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA=="
/>)
}
</Form.Group>
</Form>
)
}
</Formik>
)
}
}
function mapState(state) {
const {session} = state;
return {session}
}
const connectedLoginForm = connect(mapState, {login: loginActions.login})(LoginForm);
export {connectedLoginForm as LoginForm};
Formik validation runs, onChange, onBlur and onSubmit respectively. So in your case if you want it to be validated only on submit, you should pass validateOnChange,validateOnBlur props as false.
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema}
validateOnChange={false}
validateOnBlur={false}
onSubmit={values => onLogin(values)}>
...
/>
I am trying to do login form validation in my react app. I am new to react and I am using Material UI. So I try to enter the data in the login and password fields but I am not able to. Could someone tell me what exactly is the problem? Is it because I declared the data object in state? Following is the code:
state = {
open: false,
show: null,
dialogOpen: false,
buttonDisabled: true,
data: {
email: "",
password: ""
},
errors: {}
};
handleChange = e =>
this.setState({
data: { ...this.state.data, [e.target.name]: e.target.value }
});
onSubmit = () => {
const errors = this.validate(this.state.data);
this.setState({ errors });
};
validate = data => {
const errors = {};
if (!Validator.isEmail(data.email)) errors.email = "Invalid email";
if (!data.password) errors.password = "Can't be blank";
return errors;
};
const { data, errors } = this.state;
<Dialog open={this.state.dialogOpen} onClose={this.closeDialog} >
<DialogTitle>Login</DialogTitle>
<DialogContent>
<DialogContentText>
Please enter your Login data here
</DialogContentText>
<form onSubmit={this.onSubmit}>
<TextField
margin="dense"
id="email"
label="Email Address"
className={classes.textField}
type="email"
value={data.email}
onChange={this.handleChange}
fullWidth
/>
{errors.email && <InlineError text={errors.email} />}
<TextField
margin="dense"
id="password"
label="Password"
className={classes.textField}
type="password"
value={data.password}
onChange={this.handleChange}
fullWidth
/>
{errors.password && <InlineError text={errors.password} />}
<Button
className={classes.button}
onClick={this.clickLogin}
color="primary"
>
Enter
</Button>
</form>
</DialogContent>
</Dialog>
the issue that i noticed with your code is that you are targeting name attribute which you didn't create. so make the following adjustment to your code
handleChange = e =>
this.setState({
data: { ...this.state.data, [e.target.id]: e.target.value }
});
onSubmit = () => {
const errors = this.validate(this.state.data);
this.setState({ errors });
};
In the above code, i used e.target.id which can be referenced correctly in the textField.