handleSubmit and values not recognized in Formik form - reactjs

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.

Related

Test passing for button disabled when it should not (Formik | react-testing library)

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

why input of formik form in material-ui dont work?

I use Axios and call API for my value of input, even though I set enableReinitialize: true, but
when I want to change my input, it doesn't work
yup validation doesn't work
how can I fix these 2 problems?
const validationSchema = yup.object({
email: yup
.string("Enter your email")
.email("Enter a valid email")
.required("Email is required"),
password: yup
.string("Enter your password")
.min(8, "Password should be of minimum 8 characters length")
.required("Password is required"),
});
const FormOfFactors = () => {
const [Industry, setIndustry] = useState();
const params = useParams();
useEffect(() => {
async function fetchMyAPI() {
let response = await axios.get(
"http://nahoor.af:8080/nahoor/company/" + params.id,
);
setIndustry(response.data);
}
fetchMyAPI();
}, []);
const formik = useFormik({
initialValues: {
Industry,
},
validationSchema: validationSchema,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
console.log("bebin dorost shod?!", values);
},
enableReinitialize: true,
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<TextField
fullWidth
variant="outlined"
id="email"
name="email"
label="Email"
value={formik.values.Industry?.email}
onChange={formik.handleChange}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<TextField
fullWidth
variant="outlined"
id="password"
name="password"
label="Password"
type="password"
value={formik.values.password}
onChange={formik.handleChange}
error={formik.touched.password && Boolean(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password}
/>
<Button color="primary" variant="contained" fullWidth type="submit">
Submit
</Button>
</form>
</div>
);
};
export default FormOfFactors;
Define all your states inside Industry state with name of each field in name props in each text field and for each onChange use
onChange = {(e)=>formik.seFieldValue("stateName",e.target.value)}

Formik axios post data to contact form 7 , empty fields

I am trying to send data to contact form 7 using Formik but i get the error of fields that are empty. Can you please guide what is the problem with it. If i remove required fields from contact form 7, the form send an email with empty fields.
import * as React from 'react';
import * as Yup from 'yup';
import axios from 'axios';
import { Formik } from 'formik';
const URL = 'YOUR SITE URL';
const CF7_ID = 'YOUR CONTACT FORM ID';
const formSchema = Yup.object().shape({
formName: Yup.string().required('Required'),
formEmail: Yup.string()
.email('Invalid email')
.required('Required'),
});
/*
function convertJsontoUrlencoded(obj) {
let str = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
}
}
return str.join('&');
}
*/
const CF7 = () => {
const [state, setState] = React.useState(null || '');
return (
<>
<Formik
initialValues={{
formSubject: 'Your message',
formName: 'SAM ',
formEmail: 'salman#gmail.com',
formMessage: 'thisisisibdiad iuasdhasiud aidsuahdsiuahsd',
}}
validationSchema={formSchema}
onSubmit={(values, { setSubmitting }) => {
const submitData = async () => {
try {
const result = await axios({
//body: JSON.stringify(values),
url: `${URL}/wp-json/contact-form-7/v1/contact-forms/${CF7_ID}/feedback`,
headers: {
//Authorization: `Basic ${TOKEN}`,
'content-Type': 'application/json; charset=UTF-8',
//'content-type': 'multipart/form-data'
},
method: 'POST',
data: JSON.stringify(values),
});
setState(result.data.message);
setSubmitting(false);
} catch (error) {
setState('Something Went Wrong');
}
};
submitData();
}}
>
{({ values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting }) => (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="">
Subject
<input
type="text"
name="formSubject"
onChange={handleChange}
onBlur={handleBlur}
value={values.formSubject}
/>
{errors.formSubject && touched.formSubject ? <div>{errors.formSubject}</div> : null}
</label>
</div>
<div>
<label htmlFor="">
Name *
<input
type="text"
name="formName"
onChange={handleChange}
onBlur={handleBlur}
value={values.formName}
/>
{errors.formName && touched.formName ? <div>{errors.formName}</div> : null}
</label>
</div>
<div>
<label htmlFor="">
Email *
<input
type="email"
name="formEmail"
onChange={handleChange}
onBlur={handleBlur}
value={values.formEmail}
/>
{errors.formEmail && touched.formEmail ? <div>{errors.formEmail}</div> : null}
</label>
</div>
<div>
<label htmlFor="">
Message
<input
type="text"
name="formMessage"
onChange={handleChange}
onBlur={handleBlur}
value={values.formMessage}
/>
{errors.formMessage && touched.formMessage ? <div>{errors.formMessage}</div> : null}
</label>
</div>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
</Formik>
{state ? <p>{state}</p> : null}
</>
);
};
export default CF7;
ERROR:
into: "#"
invalid_fields: [{into: "span.wpcf7-form-control-wrap.formName", message: "The field is required.", idref: null,…},…]
message: "One or more fields have an error. Please check and try again."
posted_data_hash: ""
status: "validation_failed"

Validate Material UI TextField Submit

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.

How to validate control from material-ui in react?

I using material-ui with react.
I want to do validations such as require and maxlength and minlength. according to material-ui docs I have to use the error prop and helperText to display the error. which mean I have to trigger a function myself that check the control and display the error. :(
I wounder if this is the right way to handle validation with react, the Textfield itself can't display require message (for example)? I have to specify each error myself? for example in angular I just add require or minlength and the control display the correct error.
Or maybe there is an easy way to do it?
got it :) here my ex :
import { Link } from 'react-router-dom';
import useForm from "react-hook-form";
import * as yup from 'yup';
const LoginFormSchema = yup.object().shape({
password: yup.string().required().min(4),
username: yup.string().required().min(4)
});
export function LoginForm(props) {
const { register, handleSubmit, watch, errors } = useForm({ defaultValues, validationSchema: LoginFormSchema });
const onSubmit = data => { props.onSubmit(data); }
<div className="form-container">
<form className="form" onSubmit={handleSubmit(onSubmit)}>
<div className="form-header">
<i className="material-icons">
account_circle
</i>
<h2>Login Form</h2>
</div>
<TextField name="username" label="username" inputRef={register} />
<span className="error-message">
{errors.username && errors.username.type === "required" && "username is required"}
{errors.username && errors.username.type === "min" && "username required to be more than 4 characters"}
</span>
<TextField type="password" name="password" label="password" inputRef={register} />
<span className="error-message">
{errors.password && errors.password.type === "required" && "password is required"}
{errors.password && errors.password.type === "min" && "password required to be more than 4 characters"}
</span>
</form>
You need to install yup and formik: npm i -s yup formik
Here is a working sample with formik yup and material-ui:
import React from "react";
import { Formik, Form, useField } from "formik";
import { TextField } from "#material-ui/core";
import * as yup from "yup";
//Reusable Textbox
const MyTextField = ({
placeholder,
type = "normal",
...props
}) => {
const [field, meta] = useField<{}>(props);
const errorText = meta.error && meta.touched ? meta.error : "";
return (
<TextField
variant="outlined"
margin="normal"
type={type}
placeholder={placeholder}
{...field}
helperText={errorText}
error={!!errorText}
/>
);
};
const validationSchema = yup.object({
username: yup
.string()
.required()
.max(30)
.min(2)
.label("Username"),
password: yup
.string()
.required()
.max(30)
.min(2)
.label("Password")
});
const Signin = ({ history }) => {
return (
<div className="SignupOuter">
<Formik
validateOnChange={true}
initialValues={{
username: "",
password: "",
loading: false
}}
validationSchema={validationSchema}
onSubmit={async (data1, { setSubmitting }) => {
setSubmitting(true);
//Call API here
}}
>
{({ values, errors, isSubmitting }) => (
<Form className="Signup">
<MyTextField placeholder="Username" name="username" />
<MyTextField
placeholder="Password"
name="password"
type="password"
/>
</Form>
)}
</Formik>
</div>
);
};
export default Signin;

Resources