I noticed a weird behavior where controlled components are not respecting the minLength and allowing a form to submit even though that condition is not met.
Validation works fine for uncontrolled components.
What is the reason for this behavior?
export default function App() {
const [uncontrolledSubmitCount, setUncontrolledSubmitCount] = useState(0);
const [controlledSubmitCount, setControlledSubmitCount] = useState(0);
const [content, setContent] = useState("");
return (
<div className="App">
<h2>Uncontrolled (minLength: 10)</h2>
<form
onSubmit={(e) => {
console.log(e);
e.preventDefault();
setUncontrolledSubmitCount(uncontrolledSubmitCount + 1);
}}
>
<textarea
type="text"
minLength={10}
maxLength={30}
required
/>
<br />
<button type="submit">Submit</button>
<br />
<span>Submit Count: {uncontrolledSubmitCount}</span>
</form>
<h2>Controlled (minLength: 10)</h2>
<form
onSubmit={(e) => {
console.log(e);
e.preventDefault();
setControlledSubmitCount(controlledSubmitCount + 1);
}}
>
<textarea
type="text"
minLength={10}
maxLength={30}
required
value={content}
onChange={(e) => setContent(e.currentTarget.value)}
/>
<br />
<button type="submit">Submit</button>
</form>
<span>Submit Count: {controlledSubmitCount}</span>
</div>
);
}
Here's a reproduction link:
https://codesandbox.io/s/kind-violet-zwcct?file=/src/App.js
This behavior happens only on Chrome as far as I tested it.
Based on React official documentation adding value to textarea it overrides HTML validations and it makes it "controlled" input and it ignores it overall. But only in Chrome.
The documentation also says there is an option to use defaultValue. I tried it at it works.
<textarea
minLength={10}
maxLength={30}
required
defaultValue=""
onChange={(e) => setContent(e.currentTarget.value)}
/>
Working example from your codesanbox
With this solution you will solve Chrome problem but still keep content state with value that you enter inside textarea
NOTE:
If you try to use children value, React will throw an error that it is not supported for controlled textarea
Related
So, I was learning React Hooks and everything was going fine until the tag was added as I normally would add it like this: , however, this caused the whole page to collapse but writing it in this way, or react usual way to witting tags made it work again. any explanation behind this?
import React from 'react'
import { useState } from 'react'
function CounterHook() {
const [count, Setcount] = useState(0)
let [text, set_text] = useState("This is a Test TEXT")
let [info , set_info] = useState({name:'', email:''})
return (
<div>
<h3>{count}</h3>
<button onClick={() => Setcount(count + 1)} className='btn btn-primary'> Click </button>
<h3> {text} </h3>
<button onClick={()=> set_text("The test Text has change nothing is the same anymore ")}
className='btn btn-success'> Change Me </button>
<br />
<br />
<form>
<input type="text" className={'form-control'} value={info.name}
onChange={ event => set_info({name: event.target.value})} /> Enter your Name
<input type={'text'} className={'form-control'} value={info.email}
onChange={ event => set_info({email: event.target.value})} /> Enter your Email
{/* COMMENTED OUT CODE */} {/* that part of the code made the whole page blank */}
{/* <input type="text" className={'form-control'} value={info.name}
onChange={ event => set_info({name: event.target.value})}> Enter your Name </input>
<input type={'text'} className={'form-control'} value={info.email}
onChange={ event => set_info({email: event.target.value})}> Enter your Email </input> */}
<h2> Name is: {info.name} </h2>
<h2> Email is : {info.email} </h2>
</form>
</div>
)
}
export default CounterHook
So one problem that immediately jumps out at me is that info is supposed to be an object with the shape: {name:'', email:''} but you are setting it to {name:''} or {email:''} which will cause the object to be missing one of the object props. You are then trying to reference both props in which one of them will be undefined depending on what input you type in. Try having a two separate states for each of the values like so:
const [name, setName] = useState('');
const [email, setEmail] = useState('');
Alternatively you could try in your onChange event something like this:
This is for the name input event handler
(event)=> set_info(previousState=> {name:event.target.value, email:previousState.email})
I haven't tested option 2 but in theory it should work. Hope this helps.
I have a form component that include multiple TextFields and each one have different validation and error. Assume my TextField had custom validation prop that will take some conditions and show validation either onChange or onBlur for each TextField. I also have custom error prop that display message without condition.
const Address = ({
onChange,
required,
error,
value = {},
validation = undefined,
}: AddressProps) => {
const validator = {
validationAddress: (value: string) => (value.length === 0) ? 'Address is required' : '',
validationCity: (value: string) => (value.length === 0) ? 'City is required' : '',
}
const handleChange = (event) => {
...
}
return (
<React.Fragment>
<TextField
id="address"
label="address"
type="text"
required={required}
onChange={handleChange}
value={value.address}
validationEvent="change"
validation={validator.validationAddress}
/>
<TextField
id="city"
label="city"
type="text"
required={required}
onChange={handleChange}
value={value.city}
validationEvent="change"
validation={validator.validationCity}
/>
</React.Fragment>
export default Address;
After that I will implement my Address component in an App with submit button.
const App = () => {
const handleSubmit = () => {
...
}
return (
<Address />
<button type='submit' onClick={handleSubmit}>
Submit
</button>
)
}
export default App;
I saw many examples that input and form put together with submit button but I am struggling with handle them separately this way. My task is need to handle validation in Address component but have submit button as an optional option outside the Address component. My goal is validation message should not show until Submit button is clicked.
Try this:
move validator to parent component
pass validatorMessage useState var to Address component
call the validator by the click of submit button
Now the message is managed in the parent component and you control when the submit button displays it.
I would suggest to consider a different approach: there is no need to manually implement validation, we can use the browser built-in validation feature:
https://medium.com/p/491327f985d0
Essentially I use standard HTML mark-up and properties.
Implementation example:
export default function App() {
const onSubmit = (data: FormData) => {
console.log(Object.fromEntries(data.entries()));
};
return (
<div className="App">
<Form onSubmit={onSubmit}>
<h2>A simple form</h2>
<TextInput label="Name:" id="name" name="name" required />
<TextInput
label="Email:"
id="email"
type="email"
name="email"
required
/>
<TextInput label="Address:" id="address" name="address" />
<TextInput
label="Tel:"
id="tel"
name="tel"
type="tel"
pattern="((\+44(\s\(0\)\s|\s0\s|\s)?)|0)7\d{3}(\s)?\d{6}" // don't rely on this
/>
<button>Submit</button>
</Form>
</div>
);
}
The Form component itself is very simple:
<form
action={action}
onSubmit={handleSubmit}
noValidate
className={style.form}
>
<div className={style.wrapper}>{children}</div>
</form>
It sets the noValidate attribute to prevent the default error notification to pop up.
The input boxes are wrapped in the form element.
The form element has an onSubmit event handler: it gets triggered when user clicks on the submit button OR when the user hit return using the keyboard.
At that point we use e.preventDefault(); to prevent default form submission and handle it manually.
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.
I've been working with React for a while, and decided to try out react-hook. I'm trying to make a simple form with some simple validation, but it seems like the validation does not apply to bootstrap components. For the 'regular' input, it works fine, but for the form.control it's not working(The validation is skipped). Saw one solution where you wrap the componenet in a controller, as shown bellow, but i got the same result. Any ideas?
Thanks.
function Example(){
const { register, handleSubmit, control, errors } = useForm();
const onSubmit = (data:any) => {
console.log(data)
}
return(
<Form onSubmit={handleSubmit(onSubmit)}>
<Form.Label column>Name</Form.Label>
<Controller as={<Form.Control/>} name="firstName" control={control} ref={register({required: true})} defaultValue="" />
{errors.firstName && <p>This is required</p>}
<input name="lastName" className="form-control" ref={register({required: true})} />
{errors.lastName && <p>This is required</p>}
<input type="submit" ref={register({required: true})}/>
</Form>
)
}
I am using the following code:
<Formik initialValues={{val:cell.value}}>
<Form>
<Field type="text" name="val" size="2" onChange = {(e)=> {console.log(e.target)}}></Field>
</Form>
</Formik>
and I am unable to change the value at UI. What am I doing wrong?
Formik should be handling the changes. See the following example: https://jaredpalmer.com/formik/docs/api/formik
Note that the input in the example is triggering Formik's handleChange in the onChange:
onChange={props.handleChange}
App.js
I used functional component and useState.
const App = () => {
const cell = { value: "test" };
const [myVal, setMyVal] = useState(cell.value);
return (
<div>
<Formik initialValues={{ val: cell.value }}>
<Form>
<Field
type="text"
name="val"
size="20"
placeholder="type something"
value={myVal}
onChange={e => {
console.log(e.target.value);
setMyVal(e.target.value);
}}
/>
</Form>
</Formik>
</div>
);
};
check out the demo code
In your onChange, you need to update the state, which then gets passed back into Formik. Right now you are just outputting the value.