formik axios post form submission on reactjs - reactjs

Newbie in reactjs ,trying to build a simple form with formik and validating with Yup library .just got a problem ,how to submit axios post request in formik with validations shown in below code.i have confusion on handle submit function onSubmit
CODE
import React,{useState} from 'react'
import axios from 'axios'
import { toast } from "react-toastify";
import { useHistory } from "react-router-dom";
import config from '../../utils/config';
import { useFormik } from 'formik';
import * as Yup from 'yup';
export default function AddCompanyPage = () => {
let history = useHistory();
const formik = useFormik({
initialValues: {name:""},
validationSchema : Yup.object().shape({
name: Yup.string()
.min(2, "*Names must have at least 2 characters")
.max(20, "*Names can't be longer than 100 characters")
.required("*Name is required"),
}),
onSubmit:values=>
{
const AddCompany=async e()=> {
e.preventDefault();
axios.post(`${config.APP_CONFIG}/Companies/Company`,values)
.then(res => {
if(res.data.status_code === 200){
//setUser(res.data.msg)
history.push("/settings/companies");
}
})
.catch(err => {
toast.error("Couldnot lpost data")
})
}
},
})
return (
<h2 className="text-center mb-4">Add a Company</h2>
<form onSubmit={formik.handleSubmit}>
<div className="form-group">
<label htmlFor="Company">Company Name</label>
<input
id="name"
name="name"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.name}
/>
{formik.touched.name && formik.errors.name ? (
<div>{formik.errors.name}</div>
) : null}
</div>
<button type="submit">Submit</button>
</form>
)
}

The approach seems fine, you can try correcting the syntax of the return statement. In the return statement you should return a single element, right now you are returning 2 elements, h2 and form.
You can try wrapping it in a wrapper class or a div (not the best approach).
Also remove the word function where you are declaring/exporting the component

Related

React Formik Form not calling onSubmit function

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>

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;

Cannot read properties of undefined (reading 'getFieldProps') with the FastField in formik

I have a form with the Formik and I am going to decrease re-renders so I used the FastField.
I got the error Cannot read properties of undefined (reading 'getFieldProps') and I don't have an idea how I can solve it.
there is full code here
I found about it here and here
import React from "react";
import ReactDOM from "react-dom";
import { useFormik, FastField } from "formik";
import * as yup from "yup";
import Button from "#material-ui/core/Button";
const validationSchema = yup.object({});
const WithMaterialUI = () => {
const formik = useFormik({
initialValues: {
firstName:''
},
validationSchema: validationSchema,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
}
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<FastField name="firstName" placeholder="Weezy" />
<Button color="primary" variant="contained" fullWidth type="submit">
Submit
</Button>
</form>
</div>
);
};
ReactDOM.render(<WithMaterialUI />, document.getElementById("root"));
In the documentation you'll find
** Be aware that , , , connect(), and will NOT work with useFormik() as they all require React Context.
So you can't keep using the hook if you want to use <FastField/>, so I guess you have to fallback to the old style of wrapping your form with <Formik> component

Problem with validation: if I click one input it will show error in the another input

I really stuck up. I have a problem with my validation scheme. When I clicking on password field it shows an error also in the new password field. Validation scheme looks like right, but maybe I have missed something. So, this is my form:
And when I focusing field with current password error also is appearing inside the new password field. This is my code:
import React, { useState } from "react";
import { useFormik } from "formik";
import { useSelector } from "react-redux";
import { UserPasswordChangeSchema } from "common/types/user/userPasswordChangeSchema.type";
import { InputText, Button } from "../../components/common/index";
import { passChange } from "helpers/passChange";
import './SecurityPage.css'
import { Redirect } from "react-router-dom";
export const SecurityPage: React.FC = () => {
const email = useSelector((state: any) => state.userprofile.profile ? state.userprofile.profile.email : null)
const [makeRedirect, setMakeRedirect] = useState<boolean>(false);
const formik = useFormik(
{
initialValues: {
oldPassword: '',
password: '',
passwordConfirm: ''
},
onSubmit: async values => {
if (
await passChange({
email,
oldPassword: values.oldPassword,
newPassword: values.passwordConfirm
})
) {
setMakeRedirect(true);
}
},
validationSchema: UserPasswordChangeSchema
}
)
if (makeRedirect) {
return <Redirect to='/' />;
}
console.log(formik.touched)
return (
<div className="security">
<div className="security-info">
<h1 className="security-info-header">Security</h1>
<h2 className="security-info-subheader">Here you can change your password.</h2>
</div>
<form action="" className="security-form" onSubmit={formik.handleSubmit}>
<InputText
name={"Current password"}
propName={"oldPassword"}
value={formik.values.oldPassword}
isPassword={true}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
touched={formik.touched.oldPassword}
errorMsg={formik.errors.oldPassword}
width={451}
/>
<InputText
name={"New password"}
propName={"password"}
value={formik.values.password}
isPassword={true}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
touched={formik.touched.password}
errorMsg={formik.errors.password}
width={451}
/>
<InputText
name={"Confirm new password"}
propName={"passwordConfirm"}
value={formik.values.passwordConfirm}
isPassword={true}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
touched={formik.touched.passwordConfirm}
errorMsg={formik.errors.passwordConfirm}
width={451}
/>
<Button
isSubmit={true}
primary={true}
width={'451px'}
textCenter={true}>
Save
</Button>
</form>
</div>
)
}
And validation scheme:
import * as Yup from 'yup';
const UserPasswordChangeSchema = Yup.object({
oldPassword: Yup.string()
.trim()
.required("Password is required"),
password: Yup.string()
.trim()
.required("New password is required")
.min(8, "The password must contain 8 - 24 characters")
.max(24, "The password must contain 8 - 24 characters"),
passwordConfirm: Yup.string()
.trim()
.oneOf([Yup.ref("password"), null], "Passwords must match")
});
export { UserPasswordChangeSchema }
I wiil be very appreciated for your help
Try to import the Field and ErrorMessage component from Formik
import { Field, ErrorMessage } from 'formik';
and replace the InputText for this new Field something like:
<Field
placeHolder={"Current password"}
type="password"
name="oldPassword"
className="form-control"
value={formik.values.oldPassword}
/>
<ErrorMessage name="oldPassword" />
and repeat for the others inputs but changes the data.

Formik Material UI and react testing library

I am finding it hard to work with react testing library and understand the queries I need to use in order to select the components I need to test. The queries are too simplistic when the DOM gets more and more verbose with frameworks like material ui and formik.
I have created a code sandbox to illustrate the problem. You can check the failing test there.
https://codesandbox.io/embed/px277lj1x
The issue I am getting is, the query getLabelTextBy() does not return the component. It looks like the aria label by or the for attribute is not being rendered by material ui. Not sure how to fix this error.
The code is below here for reference too
//Subject under test
import React from "react";
import { Button } from "#material-ui/core";
import { TextField } from "formik-material-ui";
import { Field, Form, Formik } from "formik";
import * as yup from "yup";
const validationSchema = yup.object().shape({
name: yup.string().required("Office name is required"),
address: yup.string().required("Address is required")
});
export default () => (
<Formik
initialValues={{
name: "",
address: ""
}}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(false);
console.log("form is submitted with", values);
}}
render={({ submitForm, isSubmitting, isValid }) => (
<Form>
<Field
label="Office Name"
name="name"
required
type="text"
component={TextField}
/>
<Field
label="Address Line 1"
name="addressLine1"
type="text"
component={TextField}
/>
<Button
variant="contained"
color="primary"
fullWidth={false}
size="medium"
disabled={isSubmitting || !isValid}
onClick={submitForm}
data-testid="submitButton"
>
Submit
</Button>
</Form>
)}
/>
);
// Test
import React from "react";
import { render, fireEvent } from "react-testing-library";
import App from "./App";
describe("Create Office Form tests", () => {
it("button should not be disabled when all required fields are filled up", () => {
const { getByLabelText, getByTestId, debug } = render(<App />);
const values = {
"office name": "office",
address: "address 1"
};
for (const key in values) {
const input = getByLabelText(key, { exact: false, selector: "input" });
fireEvent.change(input, { target: { value: values[key] } });
}
const button = getByTestId("submitButton");
expect(button.disabled).not.toBeDefined();
});
});
You must add an id to your Field because the label's for attribute expect the ID of the input element it refers to:
<Field
id="myName"
label="Office Name"
name="name"
required
type="text"
component={TextField}
/>
A working example:
Few things. The use of { getByLabelText, getByTestId, debug } is not advised by the creator of the library. You should use screen.getByLabelText().
If there is a change, it's possible that it doesn't wait for the new render, so it would be better to await it or wrap the expect in a waitFor method.
Also, expect(button.disabled).not.toBeDefined() should not be correct. I think you're just checking if the button is disabled or not, right?
So you can use expect(button).toBeDisabled() or not.toBeDisabled()
At least to test it I think you should take off the for loop and check it normally. You can use screen.debug(component) to have the HTML shown after some action.

Resources