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)}>
...
/>
Related
I have a Formik form, using react-bootstrap for layouts and yup for validation, as follows:
import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { useParams } from "react-router-dom";
import { Formik, Field } from "formik";
import * as Yup from 'yup';
import 'bootstrap/dist/css/bootstrap.min.css';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
const VehicleForm2 = ({ action = "create" }) => {
const history = useHistory();
const VehicleValidationSchema = Yup.object().shape({
vehicleMake: Yup.string()
.required('Vehicle Make is required'),
vehicleModel: Yup.string()
.required('Vehicle Model is required')
});
const [initialValues, setValues] = useState({
vehicleMake: "",
vehicleModel: ""
});
const { id = "" } = useParams();
const handleSubmit = async (values) => {
var receivedData = "";
console.log("ENTERED SUBMIT METHOD");
try {
if (action === "create") {
await axios.post(`${process.env.REACT_APP_BASE_URL}/vehicle/add`, {
...values,
}).then(function (response) {
receivedData = response.data.Message;
});}
else {
await axios.patch(
`${process.env.REACT_APP_BASE_URL}/vehicle/edit/${id}`,
{
...values
}
);
}
history.push({pathname: '/vehicles',
state: { message: receivedData }
});
} catch (error) { console.log(error)}
};
return (
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
validationSchema={VehicleValidationSchema}
render={({handleChange, handleSubmit, handleBlur, values, errors}) => (
<Form onSubmit={handleSubmit}>
<Row>
<Col>
<Form.Label>Make</Form.Label>
<Form.Control type="text" name="make" defaultValue={initialValues.make} placeholder="Enter make" />
</Col>
<Col>
<Form.Label>Model</Form.Label>
<Form.Control type="text" name="vehicleModel" defaultValue={initialValues.vehicleModel} placeholder="Enter vehicle model" />
</Col>
<Row>
<Button variant="primary" type="submit" style={{ width: "50%", margin: "auto" }}>
{action === "create" ? "Create Vehicle" : "Edit Vehicle"}
</Button>
</Row>
</Form>
)}
/>
On loading of the Edit Vehicle form, all fields are populated as expected. However, when I change a field's value and press the Submit button, the handleSubmit function doesn't seem to be entered (because the console log at the top of the handleSubmit() function doesn't print). Instead, the page just reloads and shows the original values, and not the changes.
Also, the validation doesn't kick in when clearing an input and tabbing to the next.
Can you kindly help me identify what I am doing wrong please?
Thank you.
I think the validation is failing because you're looking for vehicleMake, instead you're passing make.
const [initialValues, setValues] = useState({
vehicleMake: "",
vehicleModel: ""
});
<Form.Control type="text" name="make" defaultValue={initialValues.make} placeholder="Enter make" />
So i think you need to update name="vehicleMake" and initialValues.vehicleMake
Have you tried removing onSubmit on Form? You only need to add onSubmit to Formik provider
<Formik onSubmit={handleSubmit}>
<Form>
</Form>
</Formik>
if the above doesn't work try using the form from formik. For example:
import Formik, {Form} from "formik"
import BootstrapForm from "react-bootstrap/Form"
...
<Formik onSubmit={handleSubmit}>
<Form>
<Row>
<Col>
<BootstrapForm.Label>Make</BootstrapForm.Label>
...
</Col>
</Row>
</Form>
</Formik>
I am new to React and especially formik. I am trying to learn how to create a login form using formik that will display a label called email with the email input. A label called password with the password input and a button called submit.
My problem is that only the two inputs and submit button displays in the browser. The two labels for email and password do not display in the browser. Please advise how I can fix this.
App.js:
import React from 'react';
import './App.css';
import FormikContainer from './components/FormikContainer';
import LoginForm from './components/LoginForm';
function App() {
return (
<div>
<LoginForm />
</div>
);
}
export default App;
LoginForm.js:
import React from 'react';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import FormikContainer from './FormikContainer';
import FormikControl from './FormikControl';
function LoginForm() {
const initialValues = {
email: '',
password: ''
};
const validationschema = Yup.object({
email: Yup.string().email('invalid email format').required('Requird'),
password: Yup.string().required('Required')
});
const onSubmit = (values) => {
console.log('Form data', values);
};
return (
<div>
<Formik
initialValues={initialValues}
validationschema={validationschema}
onSubmit={onSubmit}
>
{(formik) => {
return (
<Form>
<FormikControl
control="input"
type="email"
label="Email"
name="email"
/>
<FormikControl
control="input"
type="password"
label="Password"
name="password"
/>
<button type="submit" disabled={!formik.isValid}>
Submit
</button>
</Form>
);
}}
</Formik>
</div>
);
}
export default LoginForm;
FormikControl.js:
import React from 'react';
function FormikControl(props) {
const { control, ...rest } = props;
switch (control) {
case 'input':
return <input {...rest} />;
case 'textarea':
case 'select':
case 'radio':
case 'checkbox':
case 'date':
default:
return null;
}
return <div></div>;
}
export default FormikControl;
FormikContainer.js
import React from 'react';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import FormikControl from './FormikControl';
function FormikContainer() {
const initialValues = {
email: ''
};
const validationschema = Yup.object({
email: Yup.string().required('Required')
});
const onSubmit = (values) => console.log('Form data', values);
return (
<div>
<Formik
initialValues={initialValues}
validationschema={validationschema}
onSubmit={onSubmit}
>
{(formik) => (
<Form>
<FormikControl
control="input"
type="email"
label="Email"
name="email"
/>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
}
export default FormikContainer;
I would like to do easily this instead :
function FormikControl({ control, id, label, ...rest }) {
return (
<>
{control === "input" && <label htmlFor={id}>{label}</label>}
<input id={id} {...rest} />
</>
);
}
export default FormikControl;
I am using a formik form to sign up and testing it with react testing library. The submit button is disabled if the email or password fields are invalid. I am testing if my btn is enabled or disabled when i type certain input. I am performing 2 tests the first one with invalid inputs and the second with valid inputs but the first one is always passing even if it shouldn't. The 2nd test is behaving as it should
const MockedSignUpFormView=()=>{
return(
<BrowserRouter>
<SignUpFormView/>
</BrowserRouter>
)
}
it('btn is disabled if sign up with wrong credentials ',async () => {
render(<MockedSignUpFormView />);
const emailInput = screen.getByRole('textbox', { name: /email address/i});
const passInput = screen.getByPlaceholderText(/password/i);
const confirmPassInput = screen.getByLabelText(/confirm password/i);
const signUpBtn = screen.getByRole('button', { name: /sign up/i})
userEvent.type(emailInput,'mnbmbm#gmail.com')
userEvent.type(passInput,'sadasdas')
userEvent.type(confirmPassInput,'czxcxzcxz')
await waitFor(async ()=>{
expect(signUpBtn).not.toHaveAttribute('disabled')})
screen.debug()
});
it('btn is enabled if sign up with right credentials ',async () => {
render(<MockedSignUpFormView />);
const emailInput = screen.getByRole('textbox', { name: /email address/i});
const passInput = screen.getByPlaceholderText(/password/i);
const confirmPassInput = screen.getByLabelText(/confirm password/i);
const signUpBtn = screen.getByRole('button', { name: /sign up/i})
userEvent.type(emailInput,'mnbmbm#gmail.com')
userEvent.type(passInput,'czxcxzcxz')
userEvent.type(confirmPassInput,'czxcxzcxz')
await waitFor(async ()=>{
expect(signUpBtn).toBeEnabled()})
screen.debug()
});
I am using screen.debug and i am getting disabled on the first btn but still test keeps passing for this expect(signUpBtn).not.toHaveAttribute('disabled')}) and this expect(signUpBtn).not.toBeDisabled()
Sreen.debug() for first btn <button class="login__btn" disabled="" type="submit">
Sreen.debug() for 2nd btn <button class="login__btn" type="submit">
SignUpFormView component
const SignUpFormView=(props:any)=>{
const [error,setError]=useState(false);
const navigate=useNavigate ();
return(
<React.Fragment>
<Formik
initialValues={{
email: "",
password:"",
confirmPassword:""
}}
validationSchema={Yup.object({
password: Yup.string()
.min(6, "Must be 6 characters or more")
.required("Required"),
confirmPassword: Yup.string()
.min(6, "Must be 6 characters or more")
.required("Required")
.oneOf([Yup.ref('password'), null], 'Passwords must match'),
email: Yup.string()
.email("Invalid email address`")
.required("Required"),
})}
onSubmit={(values, { setSubmitting }) => {
props.onSubmitHandler(values.email,values.password).then((userCredential:any) => {
setSubmitting(false);
navigate('/home');
}).catch((err:any)=>{
setError(err.message);
})
}}>
{formik => {
return (
<nav className={classes["nav"]}>
<h2 className={classes['nav__detail']}>Sign Up</h2>
<Form className={classes['form__login']}>
<Input
label="Email Address"
name="email"
type="email"
/>
<Input
label="Password"
name="password"
type="password"
placeholder='Password'
/>
<Input
label="Confirm Password"
name="confirmPassword"
type="password"
/>
{(formik.isSubmitting || error) /*&& renderResponseItem(formik.isSubmitting,error)*/}
{!formik.isSubmitting && <button type="submit" disabled={!formik.isValid || formik.isSubmitting ? true :false} className={classes['login__btn']}>Sign Up</button>}
</Form>
<Link to='/'>Have an account? Login</Link>
</nav>
)
}
}
</Formik>
</React.Fragment>
)
}
Input component
import React from 'react';
import { useField } from "formik";
import classes from './InputView.module.css';
interface Props{
name:string,
id?:string,
placeholder?:string,
label:string,
type:string
}
const Input:React.FC<Props>=(props)=>{
const [field, meta] = useField(props);
const inputClasses=meta.touched && meta.error ? `${classes.input} ${classes.error}`:`${classes.input}`;
return (
<React.Fragment>
<label className={classes.label} htmlFor={props.id || props.name}>{props.label}</label>
<input className={inputClasses} {...field} {...props} id={props.name} placeholder={props?.placeholder}/>
</React.Fragment>
);
}
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.
I would like to change the state of a component based on the response of a PUT request using react-refetch.
Especially when the response of the PUT is unsuccessful, as is the case with for example a 500 response.
The following example is an example in a form. When a user submits the form it should then fire off a PUT.
If the PUT response is fulfilled, it should reset the form. Otherwise nothing should happen, and the user should be able to retry.
./MyForm.jsx
import React from "react";
import PropTypes from "prop-types";
import { PromiseState } from "react-refetch";
import { Formik, Form, Field, ErrorMessage } from "formik";
import ResetOnSuccess from "./ResetOnSuccess";
const MyForm = ({ settingsPut, settingsPutResponse }) => {
const submitForm = (values, formik) => {
settingsPut(true);
// Here it should pick up the settingsPutResponse,
// and then do the following ONLY if it's successful:
//
// formik.resetForm({ values });
// window.scrollTo(0, 0);
};
return (
<div>
<Formik
noValidate
initialValues={{ name: "", password: "" }}
onSubmit={submitForm}
>
{({ dirty }) => (
<Form>
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" />
<Field type="password" name="password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={dirty !== null ? !dirty : false}>
Submit
</button>
{settingsPutResponse && settingsPutResponse.rejected && (
<p style={{ color: "red" }}>Please try again</p>
)}
</Form>
)}
</Formik>
</div>
);
};
MyForm.propTypes = {
settingsPut: PropTypes.func.isRequired,
settingsPutResponse: PropTypes.instanceOf(PromiseState)
};
MyForm.defaultProps = {
userSettingsPutResponse: null
};
export default MyForm;
I might have a solution by creating a component:
./ResetOnSuccess.jsx
import React, { useEffect, useState } from "react";
import { useFormikContext } from "formik";
import PropTypes from "prop-types";
import { PromiseState } from "react-refetch";
const ResetOnSuccess = ({ settingsPutResponse }) => {
const { values, resetForm } = useFormikContext();
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsPutResponse && settingsPutResponse.fulfilled) {
setSuccess(true);
}
}, [settingsPutResponse]);
// only if settingsPutResponse is fulfilled will it reset the form
if (success) {
resetForm({ values });
window.scrollTo(0, 0);
setSuccess(false);
}
return null;
};
ResetOnSuccess.propTypes = { settingsPutResponse: PropTypes.instanceOf(PromiseState) };
ResetOnSuccess.defaultProps = { settingsPutResponse: null };
export default ResetOnSuccess;
And then in ./MyForm.jsx add the reset component:
<Formik
noValidate
initialValues={{ name: "", password: "" }}
onSubmit={submitForm}
>
{({ dirty }) => (
<Form>
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" />
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
// etc...
But since it's a component that returns a 'null'. This feels a bit like an anti-pattern.
Is there a better way?
I've created an codesandbox example here: https://codesandbox.io/s/quizzical-johnson-dberw