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.
Related
I'm trying to debug a react checkbox and the onChange event doesn't ever get called. I have added a console.log to test and this doesn't ever run. Here is the code for the checkbox. What is the issue?
return (
<div className="RampPane">
<div className="RampPane--content">
<p className="RampText">{transaction.merchant} </p>
<b>{moneyFormatter.format(transaction.amount)}</b>
<p className="RampText--hushed RampText--s">
{transaction.employee.firstName} {transaction.employee.lastName} - {transaction.date}
</p>
</div>
<InputCheckbox
id={transaction.id}
checked={approved}
disabled={loading}
onChange={async (newValue) => {
console.log("click")
await consumerSetTransactionApproval({
transactionId: transaction.id,
newValue,
})
setApproved(newValue)
}}
/>
</div>
)
Here is the InputCheckBox Component
return (
<div className="RampInputCheckbox--container" data-testid={inputId}>
<label
className={classNames("RampInputCheckbox--label", {
"RampInputCheckbox--label-checked": checked,
"RampInputCheckbox--label-disabled": disabled,
})}
/>
<input
id={inputId}
type="checkbox"
className="RampInputCheckbox--input"
checked={checked}
disabled={disabled}
onChange={() => onChange(!checked)}
/>
</div>
)
Use this for InputCheckBox Component
return (
<div className="RampInputCheckbox--container" data-testid={inputId}>
<label
className={classNames("RampInputCheckbox--label", {
"RampInputCheckbox--label-checked": checked,
"RampInputCheckbox--label-disabled": disabled,
})}
htmlFor={inputId}
/>
<input
id={inputId}
type="checkbox"
className="RampInputCheckbox--input"
checked={checked}
disabled={disabled}
onChange={() => onChange(!checked)}
/>
</div>
)
This works!
If the InputCheckBox is a custom built react component that renders an html input element try checking that the onChange handler is passed to the root component correctly
IF Not try including the component here to get a better insight
I am trying to create dynamic form with fieldArray in react. My code looks like this
<Formik initialValues={{
data:[],
}}
onSubmit={(values)=>{
console.log(values)
}}
>
{
(formik)=>(
<Form>
<h2>form starts here</h2>
<FieldArray name='data' render={
(arrayHelpers)=>{
return(
<div>
<button type='button' onClick={()=>arrayHelpers.insert(formik.values.data.length + 1,{question:'',quesType:'',answers:['']})}>Add</button>
{/* starts */}
{formik.values.data.map((d,index)=>(
<div key={index}>
<label htmlFor={`data.${index}.question`}>question</label>
<Field name={`data.${index}.question`} type='text' id={`data.${index}.question`}/>
<label htmlFor={`data.${index}.quesType`}>quesType</label>
<Field as="select" name={`data.${index}.quesType`}>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</Field>
</div>
// end
))}
</div>
);
}
}/>
<button type='submit'>submit</button>
</Form>
)
}
</Formik>
so after clicking add button my initialValue looksLike
data[{question:'',questype:'',answers:[]}]
Now after the select option I want to add a button that will add values in the answers array. I tried looping the d.answers but the answers property was not accessible even though I have the property. So, How to achieve this? Thanks in advance!
Why doesn't my Dropdown component work when I put it inside my <label> tag? The dropdown is displaying the options, but if you click on one, the selection is not working/displayed.
Any suggestions?
export default function App() {
return (
<div>
<div className="mt-10 mb-3 h-6 text-md uppercase font-bold>
People
</div>
<button type="button" onClick={addInvitee}>
+Add menu
</button>
<form onSubmit={handleSubmit}>
{invited.map(({ age, email, id, location, name }, index) => (
<div key={id}>
<div className="grid grid-cols-3 gap-5">
<label className="mr-3 h-6 text-md font-bold">
Names:
<input
type="text"
value={name}
placeholder="Names"
name="name"
onChange={updateInvitee(id)}
/>
</label>
//other inputs with the exact same pattern
<label>
Choice:
<Dropdown className="w-3/5" options={CHOICE} isMulti={false} />
</label>
...//other inputs
</form>
</div>
);
}
Dropdown.jsx
import React, { useState } from "react";
import Select from "react-select";
import Tag from "./Tag";
export default function Dropdown({
className,
style,
options,
styleSelect,
defaultValue,
isMulti = false
}) {
const [selected, setSelected] = useState(defaultValue);
const styles = {
select: {
width: "100%",
maxWidth: 200
}
};
return (
<div style={style}>
{selected && isMulti === false ? (
<Tag
selected={selected}
setSelected={setSelected}
styleSelect={styleSelect}
/>
) : (
<Select
className={className}
style={styles.select}
value={selected}
onChange={setSelected}
options={options}
isMulti={isMulti}
/>
)}
</div>
);
}
Here is my CodeSandbox
Firstly here's something important to note:
Based on this image, a console snippet from sandbox, it shows that the selection happens, but it gets cleared instantly.
What's the cause for this? Hmm.. let's take a look.
Consider this code snippet:
<label>
Press the text
<br /><br />
<button onClick="console.log('button-clicked')">Button</button>
</label>
Here, the <button> is placed inside a <label>. Do you notice, that when you click on press the text, the button's onClick gets triggered? ... But why?
Well, by default, a <label> is a focusable html element, and when focused, it triggers any form control element placed inside it.
So how does this relate to your code?
You have this line of code inside your <Tag> component onClick={() => setSelected(null)} and that's where the problem is. When you pick a selection, the selected gets updated and the component re-renders and displays your <Tag> component... but the event still bubbles up tree again until it reaches the <label> element. remember, at this point, it's no longer the <Select> component shown, but the <Tag> component. And guess what? the <label> gets focused and triggers the <button> inside your <Tag> component which clears (setSelected(null)) the selected state property. Once the selected is cleared, the <Dropdown> component re-renders and the <Select> component is displayed again.
This goes on and on and on as you try to select, then the process repeats.
So, from your code... Just remove this here onClick={() => setSelected(null)} and you'll see it will work. So you just need to work around it on how to clear the selected, but I have suggested a solution below.
The Solution
In your <Dropdown> component, we should try and prevent the event from bubbling. So all you need to do is add the following onClick={e=>e.preventDefault()} in your <div>
<div style={style} onClick={(e) => e.preventDefault()}>
{selected && isMulti === false ? (
<Tag
selected={selected}
setSelected={setSelected}
styleSelect={styleSelect}
/>
) : (
<Select
className={className}
style={styles.select}
value={selected}
onChange={setSelected}
options={options}
isMulti={isMulti}
/>
)}
</div>
Here's the original sandbox with the solution applied.
I have checkboxes that you can click as many as you want.
My problem is that it doesn't put a check after I uncheck. Also the values when I submit is not appearing in console.log
Pls check my codesandbox here
CLICK HERE
<Label htmlFor={id}>
<Input
type="checkbox"
id={name}
name={name}
value={value}
checked={checked}
onChange={onChange}
/>
<Indicator />
</Label>
<form onSubmit={handleSubmit}>
{products.map((product) => (
<div key={product}>
<Input
name={values.products}
value={values.products}
checked={values.products}
onChange={({ target }) => setFieldValue("products", target.value)}
/>
</div>
))}
<button type="submit">Submit</button>
</form>
when you are checking a checkbox your will get the value and check whether if the value is present already in the products list if it is not present then you should add the value ( this will be your check part ) else you can filter the value from the products ( this will be your uncheck ) .
<Checkbox
name="products"
value={product}
checked={values.products.includes(product)}
onChange={({ target }) => {
let updatedProducts;
if (values.products.includes(product)) {
updatedProducts = values.products.filter(
(product) => product !== target.value
);
} else {
updatedProducts = [...values.products, target.value];
}
setFieldValue("products", updatedProducts);
}}
/>
Working Sandbox
Sandbox
values.products is an array of your values, so it doesn't make sense to have this for your name or value. Instead, you need to pick out the specific value from the array that is relevant to your specific checkbox.
{products.map((product, index) => (
<div key={product}>
<p>{product}</p>
<Input
name={product}
value={values.products[index]}
checked={values.products[index]}
onChange={({ target }) => setFieldValue(`products.${index}`,!values.products[index])
}
/>
</div>
))}
Working Sandbox
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>