React Formik Form not calling onSubmit function - reactjs

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>

Related

React Labels not displaying in browser

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;

The React Application does not work as intended when submitting a form

I created a register page for my web Application using React. Here is my Code for Register Component -
import React, { useState } from 'react';
import { Avatar, Button, Paper, Grid, Typography, Container } from '#material-ui/core';
import axios from "axios";
import useStyles from './styles';
import Input from './Input';
const initialState = { name: '', email: '', password: '', mobile: '', confirmPassword: '' };
const Register = () => {
const [form, setForm] = useState(initialState);
const classes = useStyles();
const handleSubmit = async () => {
const { data } = await axios.post('http://localhost:4000/users/register', initialState);
console.log(data);
};
const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value });
return (
<div>
<Container component="main" maxWidth="xs">
<Paper className={classes.paper} elevation={3}>
<Typography component="h1" variant="h5">Sign up</Typography>
<form className={classes.form} onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Input name="name" label="Full Name" handleChange={handleChange} autoFocus/>
<Input name="mobile" label="Mobile Number" handleChange={handleChange}/>
<Input name="email" label="Email Address" handleChange={handleChange} type="email"/>
<Input name="password" label="Password" handleChange={handleChange} type='password'/>
<Input name="confirmPassword" label="Confirm Password" handleChange={handleChange} type="password"/>
</Grid>
<Button type="submit" fullWidth variant="contained" color="primary" className={classes.submit}>
Sign Up
</Button>
</form>
</Paper>
</Container>
</div>
);
};
export default Register;
When a form is submitted, no request is made on the server side. Instead, it redirects to the same page again with query parameters equal to the input bodies of the form. What am I doing wrong here?
You aren't preventing the default action of the form. Since you are using a standard html form, submitting it will just default to a get request which would include the values in the url like you have said.
Preventing the default will allow you to then do a non default action like the axios call you want.
const handleSubmit = async (e) => {
e.preventDefault()
const { data } = await axios.post('http://localhost:4000/users/register', initialState);
console.log(data);
};
Use event.preventDefault() in handelSubmit as it will stop the default action of the browser which is reloading on submititing a a form.
const handleSubmit = async (event) => {
event.preventDefault();
// other line of code
};

Fetch and use response to change state in React

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

React Hook Form with AntD Styling

I'm trying to figure out how to use react-hook-form with antd front end.
I have made this form and it seems to be working (it's part 1 of a multipart form wizard) except that the error messages do not display.
Can anyone see what I've done wrong in merging these two form systems?
I'm not getting any errors, but I think I have asked for both form fields to be required but if I press submit without completing them the error messages are not displayed.
import React from "react";
import useForm from "react-hook-form";
import { BrowserRouter as Router, Route } from "react-router-dom";
import { StateMachineProvider, createStore } from "little-state-machine";
import { withRouter } from "react-router-dom";
import { useStateMachine } from "little-state-machine";
import updateAction from "./updateAction";
import { Button, Form, Input, Divider, Layout, Typography, Skeleton, Switch, Card, Icon, Avatar } from 'antd';
const { Content } = Layout
const { Text, Paragraph } = Typography;
const { Meta } = Card;
createStore({
data: {}
});
const General = props => {
const { register, handleSubmit, errors } = useForm();
const { action } = useStateMachine(updateAction);
const onSubit = data => {
action(data);
props.history.push("./ProposalMethod");
};
return (
<div>
<Content
style={{
background: '#fff',
padding: 24,
margin: "auto",
minHeight: 280,
width: '70%'
}}
>
<Form onSubmit={handleSubmit(onSubit)}>
<h2>Part 1: General</h2>
<Form.Item label="Title" >
<Input
name="title"
placeholder="Add a title"
ref={register({ required: true })}
/>
{errors.title && 'A title is required.'}
</Form.Item>
<Form.Item label="Subtitle" >
<Input
name="subtitle"
placeholder="Add a subtitle"
ref={register({ required: true })}
/>
{errors.subtitle && 'A subtitle is required.'}
</Form.Item>
<Form.Item>
<Button type="secondary" htmlType="submit">
Next
</Button>
</Form.Item>
</Form>
</Content>
</div>
);
};
export default withRouter(General);
react-hook-form author here. Antd Input component doesn't really expose inner ref, so you will have to register during useEffect, and update value during onChange, eg:
const { register, setValue } = useForm();
useEffect(() => {
register({ name: 'yourField' }, { required: true });
}, [])
<Input name="yourField" onChange={(e) => setValue('yourField', e.target.value)}
i have built a wrapper component to make antd component integration easier: https://github.com/react-hook-form/react-hook-form-input
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
function App() {
const { handleSubmit, register, setValue, reset } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: true }}
name="reactSelect"
register={register}
setValue={setValue}
/>
<button
type="button"
onClick={() => {
reset({
reactSelect: '',
});
}}
>
Reset Form
</button>
<button>submit</button>
</form>
);
}
This is my working approach:
const Example = () => {
const { control, handleSubmit, errors } = useForm()
const onSubmit = data => console.log(data)
console.log(errors)
return (
<Form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="email"
control={control}
rules={{ required: "Please enter your email address" }}
as={
<Form.Item
label="name"
validateStatus={errors.email && "error"}
help={errors.email && errors.email.message}
>
<Input />
</Form.Item>
}
/>
<Button htmlType="submit">Submit</Button>
</Form>
)
}
On writing such code:
<Input
name="subtitle"
placeholder="Add a subtitle"
ref={register({ required: true })}
/>
You assume that Input reference is bound to input, but that's not true.
In fact, you need to bind it to inputRef.input.
You can check it with the next code:
const App = () => {
const inputRef = useRef();
const inputRefHtml = useRef();
useEffect(() => {
console.log(inputRef.current);
console.log(inputRefHtml.current);
});
return (
<FlexBox>
<Input ref={inputRef} />
<input ref={inputRefHtml} />
</FlexBox>
);
};
# Logs
Input {props: Object, context: Object, refs: Object, updater: Object, saveClearableInput: function ()…}
<input></input>
Note that antd is a complete UI library (using 3rd party "helpers" should "turn a red light"), in particular, Form has a validator implemented, you can see a variety of examples in docs.
In Ant Design v4.x + react-hook-form v6.x. We can implement as normally
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '#hookform/resolvers/yup';
import { useIntl } from 'react-intl';
import { Input, Button, Form } from 'antd';
const SignInSchema = yup.object().shape({
email: yup.string().email().required(),
password: yup.string().required('required').min(6, 'passwordMin'),
});
interface PropTypes {
defaultValues?: {
email: string;
password: string;
};
handleFormSubmit: SubmitHandler<{ email: string; password: string }>;
}
function SignInForm({ defaultValues, handleFormSubmit }: PropTypes) {
const intl = useIntl();
const { handleSubmit, control, errors } = useForm({
defaultValues,
resolver: yupResolver(SignInSchema),
});
return (
<Form onFinish={handleSubmit(handleFormSubmit)}>
<Form.Item
validateStatus={errors && errors['email'] ? 'error' : ''}
help={errors.email?.message}
>
<Controller
as={Input}
name="email"
autoComplete="email"
control={control}
placeholder={intl.formatMessage({ id: 'AUTH_INPUT_EMAIL' })}
/>
</Form.Item>
<Form.Item
validateStatus={errors && errors['password'] ? 'error' : ''}
help={errors.password?.message}
>
<Controller
as={Input}
name="password"
type="password"
control={control}
autoComplete="new-password"
defaultValue=""
placeholder={intl.formatMessage({ id: 'AUTH_INPUT_PASSWORD' })}
/>
</Form.Item>
<Button type="primary" htmlType="submit">
{intl.formatMessage({ id: 'SIGN_IN_SUBMIT_BUTTON' })}
</Button>
</Form>
);
}
export default SignInForm;
In case anyone is still interested in getting close to the default styles of the Form Inputs provided by Ant, here's how I got it to work:
import { Form, Button, Input } from 'antd';
import { useForm, Controller } from 'react-hook-form';
function MyForm() {
const { control, handleSubmit, errors, setValue } = useForm();
const emailError = errors.email && 'Enter your email address';
const onSubmit = data => { console.log(data) };
const EmailInput = (
<Form.Item>
<Input
type="email"
placeholder="Email"
onChange={e => setValue('email', e.target.value, true)}
onBlur={e => setValue('email', e.target.value, true)}
/>
</Form.Item>
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
as={EmailInput}
name="email"
control={control}
defaultValue=""
rules={{
required: true
}}
validateStatus={emailError ? 'error' : ''}
help={emailError}
/>
<Button block type="primary" htmlType="submit">
Submit
</Button>
</form>
);
}
Codesandbox sample

React Bootstrap + Formik - show errors after I click submit button

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)}>
...
/>

Resources