React Formik bind the external button click with onSubmit function in <Formik> - reactjs

I'm trying to submit the form by using the external buttons which are located outside of <Form> or <Formik> tags
As shown in the following screenshot, my button is in Bootstrap > Modal Footer section and they are outside of form tag. I'm trying to submit the form when the user clicks on the Submit button.
Please see the similar code as the following. I uploaded it onto CodeSandbox.
function App() {
return (
<div className="App">
<Formik
initialValues={{
date: '10/03/2019',
workoutType: "Running",
calories: 300
}}
onSubmit={values => {
console.log(values);
}}
render={({ values }) => {
return (
<React.Fragment>
<Form>
Date: <Field name="date" />
<br />
Type: <Field name="workoutType" />
<br />
Calories: <Field name="calories" />
<br />
<button type="submit">Submit</button>
</Form>
<hr />
<br />
<button type="submit" onClick={() => this.props.onSubmit}>
Button Outside Form
</button>
</React.Fragment>
);
}}
/>
</div>
);
}
Since the button is outside of the form, it doesn't trigger Submit behaviour and I don't know how to connect this button's action and Formik OnSubmit method. If I moved that button inside form tag, it works as expected and I don't have to do anything special.
I tried to follow this SO's post React Formik use submitForm outside . But I really couldn't figure out how it works.
I tried to bind the button click action like onClick={() => this.props.onSubmit} as mentioned in the post. But it's not doing anything or showing any error.
Could you please help me how I can bind the Submit button outside of the form with 'OnSubmit' function in Formik?

It appears you have access to the submitForm method as a property of the argument passed to the render function. Simply call that with the button's onClick handler...
render={({ submitForm, ...restOfProps}) => {
console.log('restOfProps', restOfProps);
return (
<React.Fragment>
<Form>
Date: <Field name="date" />
<br />
Type: <Field name="workoutType" />
<br />
Calories: <Field name="calories" />
<br />
<button type="submit">Submit</button>
</Form>
<hr />
<br />
<button type="submit" onClick={submitForm}>
Button Outside Form
</button>
</React.Fragment>
);
}}

Formik's render give you a callback param handleSubmit. Assign this to the <button.
Since your button is not in the form, change its type to <button type="button"... and assign the onClick to onClick={handleSubmit}
Update the render as follow,
render={({ values, handleSubmit }) => {
return (
<React.Fragment>
<Form>
Date: <Field name="date" />
<br />
Type: <Field name="workoutType" />
<br />
Calories: <Field name="calories" />
<br />
<button type="submit">Submit</button>
</Form>
<hr />
<br />
<button type="button" onClick={handleSubmit}>
Button Outside Form
</button>
</React.Fragment>
);
}}

This is because the handleSubmit function is never called, replace onClick={() => this.props.onSubmit} with onClick={props.handleSubmit}
edit: since it looks like you need a little more directions, here is an edited version of the linked code sandbox, the correct prop is handleSubmit and you need to destructure it from the props just like you did with values.
https://codesandbox.io/s/qz2jnlp929

Just use onClick={handlesubmit} in your external Button component making sure to destructure the handlesubmit function from the Formik component. Please have a look;
<Formik
initialValues={{field: true}}
onSubmit={() => console("Submited via my onSubmit function")}
>
{({ handleSubmit }) => (
<Form>
<Field
name="field"
placeholder="Date"
/>
</Form>
<Button type="submit" onClick={handleSubmit}>
Save
</Button>
</Formik>

Related

handleSubmit from formik is invoked whichever button I click

I want to implement a checking form for validity into the project for ordering food using formik but I have encountered some problems creating two buttons. Whichever button is clicked the handleSubmit is invoked. what can I do to solve this problem?
Function goBack only sets the state to false.
export default function Checkout(props) {
function handleSubmit(event) {
// event.preventDefault();
console.log("Hello");
}
return (
<Formik
initialValues={{ userName: "Hi", street: "", postalCode: "", city: "" }}
onSubmit={handleSubmit}
>
{(props) => (
<Form className={styles["form"]}>
<div className={styles["form-control"]}>
<div className={styles["form-control__input"]}>
<label htmlFor="userName">Your name</label>
<Field type="text" name="userName" id="userName"></Field>
</div>
<div className={styles["form-control__input"]}>
<label htmlFor="street">Street</label>
<Field type="text" name="street" id="street"></Field>
</div>
<div className={styles["form-control__input"]}>
<label htmlFor="postalCode">Postal code</label>
<Field type="text" name="postalCode" id="postalCode"></Field>
</div>
<div className={styles["form-control__input"]}>
<label htmlFor="city">City</label>
<Field type="text" name="city" id="city"></Field>
</div>
</div>
<div className={styles["form-actions"]}>
<CloseButton type="button" onClick={props.goBack}>Back</CloseButton>
<OrderButton type="submit">Confirm</OrderButton>
</div>
</Form>
)}
</Formik>
);
}
export default function CloseButton(props) {
return <button type={props.type} onClick={props.onClick} className={styles["close-button"]}>{props.children}</button>;
}
export default function OrderButton(props) {
return <button type={props.type} onClick={props.onClick} className={styles['order-button']}>{props.children}</button>
}
I wanted CloseButton to close the form and go back to the list of orders, but it only invokes handleSubmit created by Formik component instead of the function in the props. I have read the documentation but there is neither anything about creating formik with two buttons nor anything related to my problem.
Looks like in props.goBack you meant to reference the props for the component, but instead the props from within Formik are being used (as that is the closest block declaration of props). Since goBack is not defined on the Formik props, you're passing undefined as the onClick handler to the button.
The most straightforward way to fix this is to rename one of the props variables—I'd suggest naming the Formik ones to formikProps or something similar.
A better approach, in my opinion, would be to destructure the props (in both cases, though only one is necessary), like this:
export default function Checkout({ goBack }) {
// ...
return (
<Formik>
{(props) => (
// ...
<CloseButton type="button" onClick={goBack}>Back</CloseButton>
// ...
)}
</Formik>
)
}

In React with Formik how can I build a search bar that will detect input value to render the buttons?

New to Formik and React I've built a search component that I'm having issues with the passing of the input value and rendering the buttons based on input length.
Given the component:
const SearchForm = ({ index, store }) => {
const [input, setInput] = useState('')
const [disable, setDisable] = useState(true)
const [query, setQuery] = useState(null)
const results = useLunr(query, index, store)
const renderResult = results.length > 0 || query !== null ? true : false
useEffect(() => {
if (input.length >= 3) setDisable(false)
console.log('input detected', input)
}, [input])
const onReset = e => {
setInput('')
setDisable(true)
}
return (
<>
<Formik
initialValues={{ query: '' }}
onSubmit={(values, { setSubmitting }) => {
setInput('')
setDisable(true)
setQuery(values.query)
setSubmitting(false)
}}
>
<Form className="mb-5">
<div className="form-group has-feedback has-clear">
<Field
className="form-control"
name="query"
placeholder="Search . . . . ."
onChange={e => setInput(e.currentTarget.value)}
value={input}
/>
</div>
<div className="row">
<div className="col-12">
<div className="text-right">
<button type="submit" className="btn btn-primary mr-1" disabled={disable}>
Submit
</button>
<button
type="reset"
className="btn btn-primary"
value="Reset"
disabled={disable}
onClick={onReset}
>
<IoClose />
</button>
</div>
</div>
</div>
</Form>
</Formik>
{renderResult && <SearchResults query={query} posts={results} />}
</>
)
}
I've isolated where my issue is but having difficulty trying to resolve:
<Field
className="form-control"
name="query"
placeholder="Search . . . . ."
onChange={e => setInput(e.currentTarget.value)}
value={input}
/>
From within the Field's onChange and value are my problem. If I have everything as posted on submit the passed query doesn't exist. If I remove both and hard code a true for the submit button my query works.
Research
Custom change handlers with inputs inside Formik
Issue with values Formik
Why is OnChange not working when used in Formik?
In Formik how can I build a search bar that will detect input value to render the buttons?
You need to tap into the props that are available as part of the Formik component. Their docs show a simple example that is similar to what you'll need:
<Formik
initialValues={{ query: '' }}
onSubmit={(values, { setSubmitting }) => {
setInput('')
otherStuff()
}}
>
{formikProps => (
<Form className="mb-5">
<div className="form-group has-feedback has-clear">
<Field
name="query"
onChange={formikProps.handleChange}
value={formikProps.values.query}
/>
</div>
<button
type="submit"
disabled={!formikProps.values.query}
>
Submit
</button>
<button
type="reset"
disabled={!formikProps.values.query}
onClick={formikProps.resetForm}
>
</Form>
{/* ... more stuff ... */}
)}
</Formik>
You use this render props pattern to pull formiks props out (I usually call them formikProps, but you can call them anything you want), which then has access to everything you need. Rather than having your input, setInput, disable, and setDisable variables, you can just reference what is in your formikProps. For example, if you want to disable the submit button, you can just say disable={!formikProps.values.query}, meaning if the query value in the form is an empty string, you can't submit the form.
As far as onChange, as long as you give a field the correct name as it corresponds to the property in your initialValues object, formikProps.handleChange will know how to properly update that value for you. Use formikProps.values.whatever for the value of the field, an your component will read those updates automatically. The combo of name, value, and onChange, all handled through formikProps, makes form handing easy.
Formik has tons of very useful prebuilt functionality to handle this for you. I recommend hanging out on their docs site and you'll see how little of your own code you have to write to handle these common form behaviors.

React - Form submit button in parent Modal

I have a Semantic UI Form:
import {Form} from 'semantic-ui-react';
<MyForm>
<Form onSubmit={_handleSubmit}>
<Form.Input name="myInput" label="My Label" value="" />
<Form.Group>
<Form.Button>Submit</Form.Button>
</Form.Group>
</Form>
</MyForm>
This form can be displayed inside a modal, or directly in a standard view in my app
My modal looks like this:
import {Button, Modal} from 'semantic-ui-react';
<Modal open={true} size="large" centered>
<Modal.Header>My Label</Modal.Header>
<Modal.Content>
<MyForm />
</Modal.Content>
<Modal.Actions>
<Button className="close-button">Cancel</Button>
{/* Insert submit button here*/}
</Modal.Actions>
</Modal>
This simple approach is working.
What I would like to do, is to have the submit button inside the Modal.Actions section when it's displayed in a modal, and keep it right after the input otherwise.
I don't know how to tell my form that the submit button is somewhere in its parent.
I finally managed to do it using a ref.
The idea is to create a ref in the form, pointing to the submit function and having a function in props to transmit this ref to my modal.
Modal:
import {Button, Modal} from 'semantic-ui-react';
const [submitFunc, setSubmitFunc] = useState();
const submitForm = () => {
if (submitFunc) {
submitFunc.current();
}
};
<Modal open={true} size="large" centered>
<Modal.Header>My Label</Modal.Header>
<Modal.Content>
<MyForm setSubmitFunc={setSubmitFunc} />
</Modal.Content>
<Modal.Actions>
<Button>Cancel</Button>
<Button onClick={submitForm}>Submit</Button>
</Modal.Actions>
</Modal>
Form:
function EditRecordForm({setSubmitFunc}) {
const submitRef = useRef(null);
useEffect(() => {
if (!!setSubmitFunc) {
setSubmitFunc(submitRef);
}
});
const handleSubmit = () => {
// Do whatever you need to retrieve form values and submit it
}
submitRef.current = handleSubmit;
return (
<MyForm>
<Form onSubmit={_handleSubmit}>
<Form.Input name="myInput" label="My Label" value="" />
<Form.Group>
<Form.Button>Submit</Form.Button>
</Form.Group>
</Form>
</MyForm>
)
}
What you can do is, you can associate the form with the button in the modal actions using a form id. Here is how you do it :-
Form:
<MyForm>
<Form id={'my-form'} onSubmit={_handleSubmit}>
{/*Form Elements}
</Form>
</MyForm>
Modal:
<Modal.Actions>
<Button>Cancel</Button>
<Button type={'submit'} form={'my-form'}>Submit</Button>
</Modal.Actions>
Following link is the tweet by the creator of chakr-ui telling the same method to join the form in a side drawer which needs to be connected to the button in the drawer footer.
https://twitter.com/thesegunadebayo/status/1330866834636201987?lang=en

Clear a field's value on input clear in react-final-form

I'm using a custom component with react-final-form. On input change it sets the value to the address field. But when the input is cleared it doesn't update the value of the field. So I'm trying to do it with form mutators.
I have already added a mutator for clearing the field:
mutators={{
clear: ([address], state, { changeValue }) => {
changeValue(state, "address", () => undefined);
}
}}
I tried to add it to my custom onChange function, but it doesn't work.
onChange={event =>
props.input.onChange !== undefined
? props.input.onChange({ value: event })
: form.mutators.clear
}
Or maybe this can be done without mutators at all? I would really appreciate your help. Here is a live example (clearing the field works only on the button click as onClick={form.mutators.clear}).
You can just call form.change('address', undefined) at any time that you'd like to clear the value.
All the default callback are handle by the component. If you want to do a clear with a button click, you can create a custom component and use native callback methods do your thing.
onChange = (event) => {
this.setState({
address:event.target.value
});
}
onClear = () => {
this.setState({
address:''
});
}
<div>
<Field name="address">
<div>
<input
value={this.state.address}
onChange={this.onChange}
/>
</div>
</Field>
<button onClick={this.onClear}>Clear</button>
</div>
The problem is not with the react-final-form in your code, it is due to the react-da data, I have played a lot around your code within 1 day, and checked reset is working with fine with the react-final-form
Just update your render with this code and see reset is working absolutely fine. Yeah! the problem is with react da data. I am not sure about what it does due to less official information for this package.
<div className="App">
<h2>Dadata test</h2>
<Form
mutators={{
clear: ([address], state, { changeValue }) => {
changeValue(state, "address", () => undefined);
}
}}
onSubmit={onSubmit}
render={({ form, handleSubmit, pristine, invalid, values, errors }) => (
<form onSubmit={handleSubmit} noValidate>
<Field name="address" component="input" />
{/* {props => (
<div>
<ReactDadata
token="d19c6d0b94e64b21d8168f9659f64f7b8c1acd1f"
onChange={event =>
props.input.onChange !== undefined
? props.input.onChange({ value: event })
: form.mutators.clear
}
/>
</div>
)}
</Field> */}
<button type="submit" disabled={invalid}>
Submit
</button>
<button type="button" onClick={form.reset}>
Clear
</button>
<Fields names={["address"]}>
{fieldsState => (
<pre>{JSON.stringify(fieldsState, undefined, 2)}</pre>
)}
</Fields>
</form>
)}
/>
</div>
Hope it will help you to resolve the problem.

React Hooks Form Reset dont set Select to initial value

When clicking Reset, form should go back to initial values, but the Select element goes back to first option available, have used the same method for all other form elements, and they update correctly.
Have made a Codesandbox example of the problem : https://codesandbox.io/s/lively-snowflake-tf5te
function App() {
// Data for select dropdown box - selected is Lime
const SelectData = ["Grapefruit", "Lime", "Coconut", "Mango"];
// Selected Value
const selectedValue = "Lime";
// Set state
const [selectBox, setSelectBox] = useState(selectedValue);
// Submit finction
const handleSubmit = e => {
e.preventDefault();
alert(`Select Selection #1: ${selectBox}`);
};
// Reset function
const handleReset = () => {
setSelectBox(selectedValue);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>
When 'Reset' is clicked first options is selected
<br />
and not the state value.
</h2>
<form onSubmit={handleSubmit}>
<Select
label="Select Fruit"
value={selectBox}
handleChange={e => setSelectBox(e.target.value)}
data={SelectData}
/>
<br />
<br />
<input type="submit" value="Submit" />
<input type="reset" value="Reset" onClick={handleReset} />
</form>
<br />
Selectbox state value: {selectBox}
<br />
</div>
);
}
Expected result, after Resetting Form, is that then Select element value is "Lime", but it is "Grapefruit".
I changed the value to defaultValue in your DropDown.js file and it worked like charm. Check the sandbox
<select defaultValue={value} onChange={handleChange}>
{data.map(item => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
Your code is correct. Your "bug" is due to the <input type='reset'/>.
Change it to a regular button and you'll see that it works just fine.
<button onClick={handleReset}>Reset</button>
https://codesandbox.io/s/dark-cdn-qchu5
From MDN: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/reset
elements of type "reset" are rendered as buttons, with a default click event handler that resets all of the inputs in the form to their initial values.
This is concurring with your controlled component.
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>
When 'Reset' is clicked first options is selected
<br />
and not the state value.
</h2>
<form onSubmit={handleSubmit}>
<Select
label="Select Fruit"
value={selectBox}
handleChange={e => setSelectBox(e.target.value)}
data={SelectData}
/>
<br />
<br />
<input type="submit" value="Submit" />
{/* <input type="reset" value="Reset" onClick={handleReset} /> */}
<button onClick={handleReset}>Reset</button>
</form>
<br />
Selectbox state value: {selectBox}
<br />
</div>
);
Even though select is a controlled component, Form doesn't support onReset event. Thus, the behavior is similar to $form.reset() which will reset the select value to default value when button of type=Reset is clicked.
So an alternative is to use defaultValue prop in select or use a normal button with onChange prop.

Resources