React Formik onSubmit Async called twice - reactjs

I am trying to use async with onSubmit with following code for Formik in React
import React from "react";
import { Formik, Form, Field } from "formik";
import { Row, Col, Button } from "react-bootstrap";
const AddUser = () => {
const initialValues = {
name: "",
};
return (
<>
<Row className="h-100">
<Col xs={12} sm={1}></Col>
<Col xs={12} sm={10} className="align-self-center">
<div className="block-header px-3 py-2">Add Dataset</div>
<div className="dashboard-block dashboard-dark">
<Formik
initialValues={initialValues}
onSubmit={async (values, { setSubmitting }) => {
alert("hi");
setSubmitting(false);
}}
>
{({ isValid, submitForm, isSubmitting, values }) => {
return (
<Form>
<Field
name="name"
label="Name"
placeholder="Dataset Name"
/>
<Button
type="submit"
disabled={!isValid || isSubmitting}
className="w-100 btn btn-success"
onClick={submitForm}
>
Add Dataset
</Button>
</Form>
);
}}
</Formik>
</div>
</Col>
<Col xs={12} sm={1}></Col>
</Row>
</>
);
};
export default AddUser;
When I try to submit. It does alert 'hi' twice. When I don't use onSubmit as async then it works fine.
What am I doing wrong or is there any other way to perform async functionalities as I need to perform RestAPI calls?

Delete type="submit", because there is already an action onClick={submitForm}
<Button
type="submit"
disabled={!isValid || isSubmitting}
className="w-100 btn btn-success"
onClick={submitForm}
>

Be sure to NOT add both
onClick={formik.formik.handleSubmit}
and
<form onSubmit={formik.handleSubmit}>.
Should be one or the other.

I faced the same issue. Adding e.preventDefault() in my Form Submit Handler worked
for me.
onSubmitHandler = (e) => {
e.preventDefault();
//Handle submission
}

Related

How to Use antd upload with Form.Item and Form.List ,finally get uploaded filePath when onFinish?

I'm a beginner in react and antd and I want to upload file such as image or video inside Form which has Form.List and Form.Item.
I want when onFinish the form get the uploaded file path inside values.
It is working when using antd Input or Select but when using antd Upload didn't work, I think I am missing something. When using getValueFromEvent it work but contain File and FileList. I have an api take formdata and return in success the filePath I want this filePath inside values when finish the form. I hope I have explained the problem in a simple way because I will use nested Form.List.
import React, { useState } from "react";
import { Button, Col, Form, Input, Progress, Row, Upload } from "antd";
import { toastError } from "helpers/toasters";
import { customUploadFile } from "Network";
import { MinusCircleOutlined } from "#ant-design/icons";
function TestUploadWithFormItemAndFormList() {
const [filePath, setFilePath] = useState(null);
const [progressPercent, setProgressPercent] = useState(0);
function customUploadImg(options) {
const { file } = options;
const data = new FormData();
data.append("img", file);
// this is an api with axios take formData and return filePath from backEnd it correctly
customUploadFile(
data,
(progress) => {
const { loaded, total } = progress;
let progressData = (loaded / total) * 100;
if (progress) setProgressPercent(Math.round(progressData));
},
(success) => {
console.log("success", success);
setFilePath(success?.data?.filePath);
/*
output {"isSuccess":true,"data":
{"filePath":"public\\images\\1669204672383_car.jpg","fileName":"1669204672383_car.jpg"}}
*/
},
(fail) => {
console.log("fail", fail);
toastError();
}
);
}
const getFile = (event) => {
console.log("event", event);
};
const onFinish = (values) => {
console.log("values", values);
/* output
users:
Array(2)
0:{userName: 'Ali', userImage: undefined}
1:{userName: 'ahmed', userImage: undefined} why undefined??
*/
};
return (
<div>
<Form name="userData" onFinish={onFinish}>
<Form.List name="users">
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
<Row gutter={16} key={field.key}>
<Col xs={24} md={8}>
<Form.Item
name={[field.name, "userImage"]}
getValueFromEvent={getFile}
>
<Upload
customRequest={customUploadImg}
showUploadList={false}
>
<div role={"button"} type="primary">
Click me to upload
</div>
</Upload>
{filePath}
<Progress percent={progressPercent} />
</Form.Item>
</Col>
<Col xs={24} md={12}>
<div className="new-actor__name new-category ">
<Form.Item
label="user name"
className=" wd-100 "
name={[field?.name, "userName"]}
rules={[
{
required: true,
message: "Provide user name",
},
]}
>
<Input />
</Form.Item>
</div>
</Col>
<Col xs={24} md={4}>
<MinusCircleOutlined onClick={() => remove(field.name)} />
</Col>
</Row>
))}
<Button onClick={() => add()}>Add Another User</Button>
</>
)}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Save
</Button>
</Form.Item>
</Form>
</div>
);
}
export default TestUploadWithFormItemAndFormList;

Formik Fetch API Values Undefined on submit

I am new to react and I need help with submitting my form with values that was obtained from an API.
When the form is loaded, the user has to input an ID in order for it to load the remaining field and values from the API. Once the User has inserted an ID value, the user can then click on submit and the in the back end it should POST the results capture in the Console log. Currently on submit input values are Undefined.
Visit CodeSandbox link below for a working example of this
Code: https://codesandbox.io/s/formik-fetch-post-3remet
Alternatively, here is my Code:
import React, { useState, useEffect } from "react";
import "./styles.css";
import { Box, Button, TextField } from "#mui/material";
import { Formik, Form } from "formik";
export default function App() {
const [datas, setdatas] = useState([]);
const [searchId, setSearchId] = useState("");
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/users/?id=${searchId}`)
.then((Response) => Response.json())
.then((datas) => setdatas(datas));
}, [searchId]);
const handleCange = (e) => {
setSearchId(e.target.value);
};
return (
<Formik
initialValues={{ name: datas.name }}
enableReinitialize={true}
onSubmit={(data, { resetForm }) => {
console.log(data);
resetForm();
}}
>
<div className="App">
<h1>Search User(enter a value between 1-5)</h1>
<div className="searchBox">
<input
type="text"
placeholder="Enter user ID"
onChange={(e) => handleCange(e)}
/>
</div>
<div className="itemsSec">
{datas.map((datas) => (
<div key={datas.id} className="items">
<Form>
<Box>
<TextField
className="field"
label="name"
name="name"
type="text"
id="name"
variant="filled"
value={datas.name}
onBlur={Formik.handleBlur}
onChange={Formik.handleChange}
sx={{ gridColumn: "span 2" }}
key={datas.id}
>
{" "}
value={datas.name}
</TextField>
</Box>
<Button type="submit" color="secondary" variant="contained">
Submit
</Button>
</Form>
</div>
))}
</div>
</div>
</Formik>
);
}

How to submit Formik when button submit outside tag formik in ReactJs?

I have a component composition as above, the box with brown color is the parent and has a blue child box as the formix container and the right side with the green color is the container where the button is placed. is it possible to submit a form with a button outside the formix tag?
I read the documentation but still not found the solution.
you can handle it using the Formik tag innerRef reference. I added a working demo pls find here.
import React, { useRef } from "react";
import { render } from "react-dom";
import { Formik } from "formik";
import * as Yup from "yup";
import "./helper.css";
const App = () => {
const formRef = useRef();
const handleSubmit = () => {
if (formRef.current) {
formRef.current.handleSubmit();
}
};
return (
<div className="app">
<Formik
innerRef={formRef}
initialValues={{ email: "" }}
onSubmit={async (values) => {
await new Promise((resolve) => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
validationSchema={Yup.object().shape({
email: Yup.string().email().required("Required")
})}
>
{(props) => {
const {
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email" style={{ display: "block" }}>
Email
</label>
<input
id="email"
placeholder="Enter your email"
type="text"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.email && touched.email
? "text-input error"
: "text-input"
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
</form>
);
}}
</Formik>
<button type="submit" onClick={handleSubmit}>
Submit
</button>
</div>
);
};
render(<App />, document.getElementById("root"));

Reset a Formik form after clicking another Formik form

I have a react.js component which is a facet and its content is populated according to some keyword search. Every time that a new search is made, the content of the facet changes (country name - number). The facet and the search are two different Formik forms. In this example, if any of the country is checked then it is kept in the next search if the same country is also found in the following search. How is it possible to clear the facet form after every keyword search?
import React, { useState, useEffect } from 'react';
import { Formik, Field, FieldArray, Form, useFormikContext } from 'formik';
function Facet (props) {
return(
<div>
<Formik
enableReintialize={true}
initialValues={{
countries: ''
}}
onSubmit={async (values) => {
await sleep(500);
alert(JSON.stringify(values, null, 2));
}}
>
{({ values }) => (
<Form>
<FormObserver />
<b>Countries </b>
<div>
<FieldArray
name="countries"
render={arrayHelpers => (
<div>
{props.countryDist.map(countries_dist => (
<div key={countries_dist.country}>
<label>
<input
name="countries"
type="checkbox"
value={countries_dist.country}
checked={values.countries.includes(countries_dist.country)}
onChange={e => {
if (e.target.checked) {
arrayHelpers.push(countries_dist.country);
} else {
const idx = values.countries.indexOf(countries_dist.country);
arrayHelpers.remove(idx);
}
}}
/>
{" " + countries_dist.country +
" (" + countries_dist.count + ")"}
</label>
</div>
))}
</div>
)}
/>
</div>
</Form>
)}
</Formik>
</div>
);
}
export default Facet;
Edit:
Search.js
import React from 'react';
import { Formik } from 'formik';
import { Button, Col, Form, Row } from 'react-bootstrap';
function Search (props) {
const onSubmit_func = async (values, actions) => {
await props.search_p(values.query);
}
return (
<Formik
initialValues={{
query: ''
}}
onSubmit={onSubmit_func}
>
{({
handleChange,
handleSubmit,
setFieldValue,
values
}) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId='query'>
<Row>
<Col>
<Form.Control
autoFocus
type='text'
name='query'
value={values.query}
onChange={e => {
setFieldValue('countries', [])
handleChange(e)
}}
/>
</Col>
<Col>
<p>
<Button type='submit' variant='primary'>Search</Button>
</p>
</Col>
</Row>
</Form.Group>
</Form>
)}
</Formik>
);
}
export default Search;
As I understand you expect that on every change of props.countryDist (populated according to some keyword search) checked countries will resetted.
I suggest to use setFieldValue as a method to reset countries on change of search keyword in search component:
setFieldValue('countries', [])

Unable to set `isSubmitting` with Formik

Edit
As it turns out, it was working all along -- the issue was because my handleLogin method was async
New sandbox:
I have a basic Form component. It passes setSubmitting as one of the available methods, and it passes isSubmitting as well. I want to disable the submit button while the form is submitting, but I'm having trouble with this.
Initially, I had a <form> element and I was trying to set setSubmitting(true) in the below part:
<form
onSubmit={(credentials) => {
setSubmitting(true); // <--
handleSubmit(credentials);
}}
>
But this didn't work. So I've tried getting rid of the <form> and changing <Button> to type="button" instead of submit, and I did,
<Button
color="primary"
disabled={isSubmitting}
fullWidth
size="large"
type="button"
variant="contained"
onClick={() => {
setSubmitting(true);
handleLogin(values);
}}
>
Submit
</Button>
But the problem with this, is that in order to do setSubmitting(false) in case of an error is that I have to do this,
onClick={() => {
setSubmitting(true);
handleLogin(values, setSubmitting); // <--
}}
And in addition to this, I have no use for onSubmit={handleLogin}, but if I remove that, Typescript complains.
There's got to be an easier way to accomplish this (without using useFormik).
What can I do here?
Here is the component:
import * as React from "react";
import { Formik } from "formik";
import { Box, Button, TextField } from "#material-ui/core";
const Form = React.memo(() => {
const handleLogin = React.useCallback(async (credentials, setSubmitting) => {
console.log(credentials);
setTimeout(() => {
setSubmitting(false);
}, 2000);
}, []);
return (
<Formik
initialValues={{
email: ""
}}
onSubmit={handleLogin} // removing this line make Typescript complain
>
{({
handleSubmit,
handleChange,
setSubmitting,
isSubmitting,
values
}) => (
<div>
<TextField
fullWidth
label="Email"
margin="normal"
name="email"
onChange={handleChange}
value={values.email}
variant="outlined"
/>
<Box sx={{ my: 2 }}>
<Button
color="primary"
disabled={isSubmitting}
fullWidth
size="large"
type="button"
variant="contained"
onClick={() => {
setSubmitting(true);
handleLogin(values, setSubmitting);
}}
>
Submit
</Button>
</Box>
</div>
)}
</Formik>
);
});
export default Form;
You forget to put the form inside your Formik component
<Formik>
{...}
<form onSubmit={handleSubmit}>
{...}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
</Formik>
so now you can use your button as submit.
demo: https://stackblitz.com/edit/react-egp1gc?file=src%2FForm.js

Resources