I'm using React Hook Form to make an input for height, weight and length. The value cannot be 0 - it has to be at least 1 or more. I tried different ways such as: pattern with regex, validate and more. But I am unable to figure out how to prevent the input field from letting the user type in '0' ( only as the first character ). I also tried type="number" and min="1" however it's not changing anything. Any ideas would be greatly appreciated.
<Input
{...form.register(`pallets.${index}.weight`, {
required: t('errors.emptyInput'),
pattern: {
value: /^[^1-9]/,
message: 'hello',
},
validate: (value) => {
return [/^[^1-9]/].every((pattern) => pattern.test(value))
},
})}
type='number'
/>
It appears to me that pattern does not change anything. Only type= 'number' actually affects the way the input field works.
<input
type="number"
{...register("test", {
min: 1
})}
/>
You can check the detailed usage from the documentation.
You can take advantage of react useState hook to implement this functionality.
export default function App() {
const [x, setX] = useState(null);
const handleChange = e => {
const value = e.target.value;
if (!value || /^[1-9]\d*/.test(value))
setX(e.target.value)
}
return (
<div className="App">
<input type="number" value={x} onChange={handleChange} />
</div>
);
}
Related
I have a react-final-form form, which has 2 inputs. Let's call them from and to.
What I want to do is that whenever input from changes, I set a value for input to based on input from's value.
I do that in validate function because i don't know where else i can do that. And it causes re-rendering the component in a loop.
Since I change the value of to in validate, it causes validate function to run again and again and again. How can I avoid that?
The actual code is much more complex than this but this is where i run into problems.
Thanks
const validate = (v) => {
const calculateFrom = calculate(v.from);
window.setTo(calculateFrom);
};
<Form
onSubmit={onSubmit}
validate={validate}
mutators={{
setTo: (a, s, u) => {
u.changeValue(s, 'to', () => a[0]);
},
setMax: (a, s, u) => {
u.changeValue(s, 'from', () => getMaxBalance(selectedAsset1));
},
}}
subscription={{ submitting: true, pristine: true }}
render={({
form,
pristine,
invalid,
handleSubmit,
}) => {
if (!window.setTo) {
window.setTo = form.mutators.setTo;
}
return (
<form onSubmit={handleSubmit}>
<Field name="from">
{({ input, meta }) => (
<Input
type="number"
placeholder="123"
size="input-medium"
input={input}
meta={meta}
/>
)}
</Field>
<Field name="to">
{({ input, meta }) => (
<Input
type="number"
placeholder="123"
size="input-medium"
input={input}
meta={meta}
/>
)}
</Field>
/>
First of all you could use an existing decorator
https://codesandbox.io/s/oq52p6v96y
I'm not really sure why it is re-rendering infinitely. Might have something to do with reusing the function setTo that you put on the window.
If you don't want to add that mutator library I'd try the following solutions.
use parse prop on the the Field where you can get the value and compare it with the other value that you need and return exactly what is should be check attached example parse prop
Final form allows to nest Fields inside custom components so you could just handle everything there by using useForm hook
Previously I used to write like this:
<input className="form-control" name="productImage" type='file' onChange={handleImageUpload} ref={register({ required: true })} />
After the update I have to write like this:
<input className="form-control" type="file" {...register('productImage', { required: true })} />
How do I use onChange={handleImageUpload} on the updated version of React Hook Form?
Here is the migration docs
Please pardon my mistakes in the manner of asking the question. I'm new to these things.
Thank you.
https://github.com/react-hook-form/react-hook-form/releases/tag/v7.16.0
V7.16.0 has introduced this new API for custom onChange.
<input
type="text"
{...register('test', {
onChange: (e) => {},
onBlur: (e) => {},
})}
/>
You just have to move the onChange props after {...register(...)}
const productImageField = register("productImage", { required: true });
return (
<input
className="form-control"
type="file"
{...productImageField}
onChange={(e) => {
productImageField.onChange(e);
handleImageUpload(e);
}}
/>
)
(Dec 3 2021) edit: this approach is no longer correct since react-hook-form v7.16.0's changes, see #Bill's answer.
In register documentation https://react-hook-form.com/api/useform/register, sample exists on Custom onChange, onBlur section :
// onChange got overwrite by register method
<input onChange={handleChange} {...register('test')} />
// register's onChange got overwrite by register method
<input {...register('test')} onChange={handleChange}/>
const firstName = register('firstName', { required: true })
<input
onChange={(e) => {
firstName.onChange(e); // method from hook form register
handleChange(e); // your method
}}
onBlur={firstName.onBlur}
ref={firstName.ref}
/>
So for your case :
const productImageRegister = register("productImage", {required: true})
<input className="form-control"
type="file"
{...productImageRegister }
onChange={e => {
productImageRegister.onChange(e);
handleImageUpload(e);
}} />
For me, decoration solution worked
const fieldRegister = register("productImage", {required: true})
const origOnChange = fieldRegister.onChange
fieldRegister.onChange = (e) => {
const res = origOnChange(e)
const value = e.target.value
// do something with value
return res
}
For field declaration use
<input {...fieldRegister}/>
Was stuck with the same problem. For me the problem was that my onChange was above the react-hook-form's {...register} and moving it below the register solved the problem for me!!
I faced a similar issue recently when migrating to V7. If it can help anybody.
A parent component handling the form was passing down to a wrapper the register function, the wrapper passing it down again to an input that needed debouncing on change.
I called the register formLibraryRef in case I wanted to use a different library later but overall I had to do something like:
const { onChange, ...rest } = formLibraryRef(inputName);
pass the onChange to the function that is itself passed to the native onChange event of the input:
const handleDebouncedChange: (event: React.ChangeEvent<HTMLInputElement>) => void = (
event: ChangeEvent<HTMLInputElement>,
) => {
onChange(event);
if (preCallback) {
preCallback();
}
debounceInput(event);
};
and then pass the rest to the input:
<input
aria-label={inputName}
name={inputName}
data-testid={dataTestId}
className={`form-control ...${classNames}`}
id={inputId}
onChange={handleDebouncedChange}
onFocus={onFocus}
placeholder={I18n.t(placeholder)}
{...rest}
/>
The register section in the docs here: https://react-hook-form.com/migrate-v6-to-v7/ gives a bit more info on how to get the onChange and shows an example for Missing ref.
You can use react-hook-form control
<Controller
render={({ field }) => <input onChange={event=>{
handleImageUpload(event);
field.onChange(event);
}} />}
name="image"
control={control}
/>
I have a number stored in my React state, and I want to edit it using an <input type="number" />. Here's my attempt:
function MyNumberInput() {
const [number, setNumber] = useState(42);
return (
<input
type="number"
value={number}
onChange={event => {
setNumber(parseFloat(event.target.value)); // Convert string value to a number!
}}
/>
);
}
Try it on JSFiddle.
The problem is that this code fails during intermediate, non-number states. If I want to enter -12, I begin by typing the negative sign. When I do, the onChange handler fires, parseFloat("-") returns NaN, and the input remains empty. As a user, it feels like my input was completely ignored.
just use unary plus operator to convert a string into a number like this:
function MyNumberInput() {
const [number, setNumber] = useState(42);
return (
<input
type="number"
value={number}
onChange={event => {
setNumber(+(event.target.value)); // Convert string value to a number!
}}
/>
);
}
Use RegEx
The problem is the input get NaN is because the input type="number" and - or . is not a number.
I suggest to use RegEx:
function MyNumberInput() {
const [number, setNumber] = useState(42);
const updateNumber = ({ target }) => {
const reg = new RegExp('^[0-9+-/]+$|^$');
if (reg.test(target.value)) setNumber(target.value);
};
return (
<input type="text" value={number} onChange={updateNumber} />
);
}
You may want to remove the below props to enable non-number content input.
type="number"
And remove the parseFloat to store the string like - or .0
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [number, setNumber] = useState('');
return (
<div className="App">
<input
value={number}
onChange={event => {
// console.log(event.currentTarget.value);
setNumber(event.target.value);
}}
/>
</div>
);
}
Try it online:
I have this validation schema for a form made using withFormik() used in my React application, Here validateJql() is my custom validation function for yup
validationSchema: Yup.object().shape({
rework: Yup.string().required("Rework query is required").validateJql(),
originalEstimate: Yup.string().required("Original Estimate query is required").validateJql()
})
and my form Component is like this:
const addSomeForm = (props) => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleSubmit,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<div>
<label htmlFor="name" className="col-form-label"><b>Rework Query:</b></label>
<textarea id="query.rework" rows="5" type="text" className="form-control" placeholder="Enter JQL with aggregate Function" value={values.query.rework} onChange={handleChange} required />
{errors.query && errors.query.rework && touched.query && <span className="alert label"> <strong>{errors.query.rework}</strong></span>}
</div>
</div>
<div className="form-group">
<div>
<label htmlFor="name" className="col-form-label"><b>Original Estimate:</b></label>
<textarea id="query.originalEstimate" rows="5" type="text" className="form-control" placeholder="Enter JQL with aggregate Function" value={values.query.originalEstimate} onChange={handleChange} required />
{errors.query && errors.query.originalEstimate && touched.query && <span className="alert label"> <strong>{errors.query.originalEstimate}</strong></span>}
</div>
</div>
</form>
)
Now, what I want to do is not to run validation on form submit if the field rework and originalEstimate is not touched and also not empty. How can I achieve this with withFormik HOC or Yup? I have partially been through Yup docs and Formik docs but could not find something to fit with my problem.
This is the case after submitting the form once and editing after that for minor tweaks in some of those multiple fields. if there are multiple fields and only some are edited, I don't want to run validation for all the fields existed.
Thank you in advance.
This is the default desired behavior as stated in formik docs but i think you can do the following:
Instead of using validationSchema, use validate function.
Validate function will work the same way your validationSchema works. You just need to use Yup programmatically from a function with mixed.validate
So you can have the full control of all the props in your form. You could also use the getFieldMeta to get the touched and value of the field and use that in your validation. Or get those props from touched object in form with getIn
Something like:
// Some util functions
function mapYupErrorsToFormikErrors(err: { inner: any[] }) {
return err.inner
.filter((i: { path: any }) => !!i.path)
.reduce(
(curr: any, next: { path: any; errors: any[] }) => ({
...curr,
[next.path]: next.errors[0],
}),
{},
)
}
function validateSchema(values: object, schema: Schema<object>) {
return schema
.validate(values, {
abortEarly: false,
strict: false,
})
.then(() => {
return {}
})
.catch(mapYupErrorsToFormikErrors)
}
// Your validation function, as you are using `withFormik` you will have the props present
function validateFoo(values, props) {
const { touched, value } = props.getFieldMeta('fooFieldName') // (or props.form.getFieldmeta, not sure)
const errors = validateSchema(values, yourYupSchema)
if (!touched && !value && errors.fooFieldName) {
delete errors.fooFieldName
}
return errors
}
Well, touched might not work for your use case because formik probably would set it to true on submission, but there you have all the props and you can use something different, like the empty value or some other state prop you manually set. You got all the control there.
I had a similar issue, I ended up creating another field where I set the value when showing the edit screen. Then i compare inside a test function like this :
originalField: yup.string().default(''),
field: yup.string().default('').required('Field is required.').test('is-test',
'This is my test.',
async (value, $field) => {
if($field.parent.originalField !== '' && value === $field.parent.originalField) return true
return await complexAsyncValidation(value)
}
Not perfect, but definitely working
I'd like my controlled input to initialize with no value in the box.
The input is a number, so I don't pass in an empty ''.
Using defaultProps, I initialize the input with a null.
When typing into the input the console reports this message:
<MyInput> is changing an uncontrolled input of type number to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa).
To prevent this normally I initialize with an empty string to prevent this "switching" from happening. But with a number (I don't want to show a 0, I want nothing to show) I am not sure how to do it.
static defaultProps = {
estimatedHours: null,
estimatedMinutes: null,
}
defalut values ^^
<input
type="number"
onChange={(e) => this.handleChange('Hours', e.target.value)}
onKeyDown={this.handleKeyDown}
onPaste={this.handlePaste}
value={estimatedHours}
placeholder={hoursText}
className="slds-input th-trailmix-textbox th-time-component__input"
/>
Simpler approach is to have a fallback value be an empty string.
Works for type=number as well.
<input type="number" value={estimatedHours || ''} />
You can set value Undefined,
Also add a check for input type if required
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {value: undefined};
this.handleChange = this.handleChange.bind(this);
}
handleChange = (event) => {
const isnum = /^\d+$/.test(event.target.value);
if (isnum) {
return this.setState({
value: Number(event.target.value),
});
}
};
render() {
return (
<input
type="number"
onChange={this.handleChange}
value={String(this.state.value)}
placeholder={"Please enter number"}
/>
);
}
}
and convert String to Number and vice versa
This won't throw you an error for uncontrolled input
I'd like my controlled input to initialize with no value in the box.
my problem is that I want a controlled number input with a null /
undefined initial value.
I suggest defaulting the value props in the input element with an empty string like the following:
<input
type="number"
// ...
value={estimatedHours === undefined ? '' : estimatedHours}
/>
This will make the input empty and prevent the warning from React. It'll also conveniently work well with the required prop too so the user must enter an input.
In TypeScript 3.7+, it can be shorted to the following using the nullish coalescing operator:
<input
type="number"
// ...
value={estimatedHours ?? ''}
/>
Uncontrolled inputs are simply inputs where you aren't tying value to JavaScript. So if you are rendering:
<input value={any} />
That is a controlled input.
If you want to render an uncontrolled input with an empty value you could simply write
<input type="number" /> or <input type="number" placeholder="" />
or <input type="number" placeholder="please enter a number..." />
Next, if you want to pull the value use a ref.
state = {
query: ''
}
handleSubmit = (e) => {
e.preventDefault()
this.setState({ query: this.search.value })
}
render() {
return (
<form>
<input
type="number"
ref={input => this.search = input}
placeholder=""
/>
<button type="submit" onClick={this.handleSubmit}>Submit</button>
</form>
)
}
Additionally, you can still perform logic on the return value if you need to. ie:
handleSubmit = (e) => {
e.preventDefault()
this.setState({ query: this.search.value.toExponential(2) })
// square whatever the user inputs and store in this.state.query
}
The easiest way to get the behaviour you are looking for is to define the prop type for this field to a string. This will allow '' to be used as a default prop. On form submission, you will need to make sure that the value of this field is parsed to a number.
type FormProps = {
estimatedHours: string;
estimatedMinutes: string;
}
FormProps.defaultProps = {
estimatedHours: '',
estimatedMinutes: '',
}