react-hook-form custom validation message not showing - reactjs

I'm using react-hook-form 6.8.2 with React 16.13.1 and normal validation works as expected, but not when inside of the "validate" key.
const { trigger, formState, watch, reset } = useFormContext({
mode: 'all',
});
--
ref={register({
required: 'This is required',
minLength: {
value: 3,
message: 'Length must be 3 or more',
},
validate: {
always: (value) => value === 1 || 'This should almost always trigger',
},
maxLength: {
value: 5,
message: 'max 5',
},
})}
required, minLength and maxLength all work, but always doesn't.
I have tried this:
always: (value) => value === 1 || console.log('This should almost always trigger') which logs the error in the console
and I have tried this: validate: (value) => value === 1 || 'This should almost always trigger' which does nothing either
Why isn't the validation message shown in the UI?

I made a CodeSandbox with your requirements using react-hook-form#6.8.2 and react#16.13.1 also using RHF's useFormContext hook. It's working fine, have a look:
One thing that isn't correct in your example code: you're passing the useForm config options to the useFormContext hook. That isn't working, you have to pass them instead to your useForm call. Check the CodeSandbox to see how to set the mode to 'all'.

Turns out that there was a custom error rendering class, hidden deep within our own library for input fields, that didn't account for custom error types.
:(

In: V7
Instead of passing the register function's result to a ref, just call the register function with a spread operator on the input field. After this, you need a unique name for this input, just like for every input. In my example, your config works fine. https://codesandbox.io/s/react-hook-form-js-forked-js0il?file=/src/App.js
Leave this answer for someone who looking for a similar solution.

The validate function checks for your condition. If it returns false then the message can be displayed using the error mechanism like this:
import React from "react";
import { useForm } from "react-hook-form";
const Example = () => {
const { handleSubmit, register, errors } = useForm();
const onSubmit = values => console.log(values);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("username", {
validate: value => value !== "admin" || "Nice try!"
})}
/>
{errors.username && errors.username.message} //Here we display the error
<button type="submit">Submit</button>
</form>
);
};

Related

How to make an input of type number a controlled component in react

export default function Form() {
const [user, setUser] = useState({
name: "",
numOfQs: 0
})
console.log(user)
function handleUserDataChange(event) {
setUser(prevUser => {
return {
...prevUser,
[event.target.name]: event.target.value
}
})
}
return (
<>
<input
type="text"
placeholder="username"
name="name"
value={user.name}
onChange={handleUserDataChange} />
<input
type="number"
name="numOfQs"
value={user.numOfQs}
onChange={handleUserDataChange} />
</>
)}
I was trying to build my form using react, and when I tried to use input[type: number] on the form field it was giving me this error, don't know why. I was reading through react docs about forms, and everything from the checkbox, radio buttons, textarea was all working fine. but when I used an input element of the type number, I got the following error.
*!Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property target on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist(). See fb.me/react-event-pooling for more information.
so, the problem only arises when an input of type "number" is introduced. when I remove it all of my other form elements work fine.
I'm still in the learning phase of react. please help me out.
This happened because the event that passed into the function is used as an asynchronous event.
To fix this, decompose the event object
function handleUserDataChange(event) {
const { name, value } = event.target;
setUser(prevUser => {
return {
...prevUser,
[name]: value
}
})
}

Focus On Input With Error on Form Submission

It would be nice to have functionality when the user submits a form that fails the validation, it will set focus on the first field with an error.
I have shouldFocusError : true set in the useForm configuration. When I click submit, the validation triggers, but the input is not focused.
Any suggestions on how I can achieve this?
For reference, I am using MUI and react-hook-form to create custom components in the form. For the components themselves, they are using the useController hook like so:
const { field, fieldState } = useController(props);
<TextField
{...field}
error={!!fieldState.error}
helperText={fieldState.error?.message || ''}
/>
Solution:
Spreading the field object returned from the useController hook contains the following:
{
value: ...
name: ...
onChange: ...
onBlur: ...
ref: ...
}
The problem I described above was solved when I removed ref property from the object and passed it separately to inputRef. Like so:
const { field, fieldState } = useController(props);
const { ref, ...fieldProps } = field;
<TextField
{...fieldProps}
inputRef={ref} //or you can use field.ref
id={field.name} //this solved other issues I was having
error={!!fieldState.error}
helperText={fieldState.error?.message || ''}
/>
After doing this, React Hook Form set focus on the first field with an error when the user submits a form that fails the validation.

How do I override the defaultValues in useForm and maintain the isDirty function?

I have a situation where I need to call my DB to obtain some default values. But in the off-chance the data does not exist, I will set some default values in my useForm. Basically, this means that the defaultValues declared in useForm is a fallback value if I fail to obtain the default values from the DB.
From what Im understanding, according to the documentation with regards to useForm,
The defaultValues for inputs are used as the initial value when a component is first rendered, before a user interacts with it.
Or basically, the useForm is one of the first things defined when the page is loaded.
So, unless I can call my DB before useForm is loaded, Im a little stuck on this.
I've read that each Controller field can have something called defaultValue which sounds like the solution, but the documentation mentioned a caveat of
If both defaultValue and defaultValues are set, the value from defaultValues will be used.
I considered setValues but I want to use the isDirty function, which allows field validation and the value used to check whether the form is dirty is based on the useForm defaultValues. Thus, if I were to use setValues, the form would be declared dirty, which is something I do not want.
TL;DR this is what I want:
This is my initial value(the fallback value, result A).
const { formState: { isDirty }, } = useForm(
{defaultValues:{
userID: "",
userName: "",
userClass: "administrator",
}}
);
What I want to do is to make a DB call and replace the data, so that it would now look something like this(result B) if the call is successful(if fail, it will remain as result A).
const { formState: { isDirty }, } = useForm(
{defaultValues:{
userID: "1",
userName: "user",
userClass: "administrator",
}}
);
Please note that the DB call will replace only the userID and userName default values, the default value for userClass will be maintained.
So, the flow is as such:
Case 1: render form -> result A -> DB call -> success -> result B
Case 2: render form -> result A -> DB call -> fail/no data -> result A
So, unless I actually key in an input that is different from the default values of either results depending on the case, both Case 1 and Case 2 should return isDirty==false when I check it.
For react-hook-form#7.22.0 and newer
I think you want to use reset here in combination with useEffect to trigger it when your DB call has finished. You can additionally set some config as the second argument to reset, for example affecting the isDirty state in your case.
Although your answer works there is no need to use getValues here, as reset will only override the fields which you are passing to reset. Your linked answer is for an older version of RHF, where this was necessary.
Also if you're not adding a field dynamically on runtime then you can just pass the whole object you get from your DB call to reset and set the shouldUnregister to true in your useForm config. This way props from your result object which haven't got a corresponding form field will get ignored.
export default function Form() {
const { register, handleSubmit, reset, formState } = useForm({
defaultValues: {
userID: "",
userName: "",
userClass: "administrator"
},
shouldUnregister: true
});
console.log(formState.isDirty);
const onSubmit = (data) => {
console.log(data);
};
const onReset = async () => {
const result = await Promise.resolve({
userID: "123",
userName: "Brian Wilson",
otherField: "bla"
});
reset(result);
};
useEffect(() => {
onReset();
}, []);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>User ID</label>
<input type="text" {...register("userID")} />
<label>User Name</label>
<input type="text" {...register("userName")} />
<label>User Class</label>
<input type="text" {...register("userClass")} />
<input type="submit" />
</form>
);
}
Here is a Sandbox demonstrating the explanation above:
For older versions
Just merge your defaultValues or field values via getValues with the result of your DB call.
export default function Form() {
const {
register,
handleSubmit,
reset,
formState,
getValues,
control
} = useForm({
defaultValues: {
userID: "",
userName: "",
userClass: "administrator"
},
shouldUnregister: true
});
console.log(formState.isDirty);
const onSubmit = (data, e) => {
console.log(data);
};
const onReset = async () => {
const result = await delay();
reset({ ...getValues(), ...result });
};
useEffect(() => {
onReset();
}, []);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>User ID</label>
<input type="text" {...register("userID")} />
<label>User Name</label>
<input type="text" {...register("userName")} />
<label>User Class</label>
<Controller
name="userClass"
control={control}
render={({ field }) => <input type="text" {...field} />}
/>
<input type="submit" />
</form>
);
}
I went and further googled the issues I had with #knoefel's original answer, and I came across this
Thus, the solution I came up with, based on a combination with #knoefel's answer and the answer in the link:
useEffect(async () => {
let dataArray={};
let result= fetch('my-db-call');
if(result) {
dataArray['userID']=result.userID
dataArray['userName']=result.userName
}
reset({...getValues(), ...dataArray})
}, [])
Apparently, what happens is that the the reset function will first set the values, result A, using ...getValues() and any subsequent data after will replace the previously set values only if it exist. (eg. if my dataArray object lacks the userID key, it will not replace the userID default with the new default. ).
Only after both getValues and dataArray is set then will it reset the default values.
As far as I can tell, this is more or less incline with what I need and should give me result B.

Is there some way to validate redux-form in a custom input?

I need to throw a validation error from my custom input component, and not let the user successfully submit the form. In this Codesandbox, I'm passing the maxLength prop to the Field with a custom input component RenderField. I want to validate the maxLength within the RenderField (see the comments in the Codesandbox).
Some observations:
I can't pass a validate prop to the Field, neither at reduxForm decorator. This is due to an architecture decision.
The max length example at the Sandbox is only to illustrate what I need. I know I can pass maxLength to the input component if I need to validate it, but my real problem is not about a string's max length.
Not sure if it will fit your needs but you can add your validation rules into onSubmit function which in your case is showResults
export default (async function showResults(values) {
await sleep(500); // simulate server latency
const { firstName } = values;
if (firstName.length < 5)
throw new SubmissionError({ firstName: "too short" });
window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`);
});
then you can clean up the RenderField
const RenderField = ({
input,
placeholder,
type,
maxLength,
meta: { error, dispatch, ...restMeta },
...rest
}) => {
return (
<div>
<input
{...input}
placeholder={placeholder}
type={type}
style={{ border: 0, width: "100%" }}
/>
<p>{error}</p>
</div>
);
};
Line below won't work because it is mutation, I don't see action creator which will be able to handle this properly.
if (errorMessage) error = errorMessage;
Also you can consider to track state of the button and show errors only if it was clicked.
But IMO the best solution would be really to handle validation in redux-form.

How to clear some fields in form - Redux-Form

I'm working on a page which has many input validations and logical bindings on it and every sprint iteration the page size increasing. So that, I have to find a beautiful and scalable solution.
Imagine, when user select a value from dropdown as 'A', some fields must be disabled, some fields must be cleared and some fields initilized with default values. I can change one related field (doesn't have validation rule like regexp or lenght constrait) value with some little code like
this.props.dispatch(change('xForm','xField','xValue' ))
My problem is that when I need to clear multiple fields,
It always blocked by my validation method and clear operation is failed ( Note : I supposed to be like that but not like that)
.
I tried some strategies as below but y,z,w fields have some text and it triggered validation rule and hanled errors. So that, inputs have still old values, not cleared ones.
//Clear
this.props.dispatch(change('xForm','yField','' ))
this.props.dispatch(change('xForm','zField','' ))
this.props.dispatch(change('xForm','wField','' ))
What are the best practises for clear inputs or assign some values to inputs in redux-form for pages which have highly dependent inputs.
I have been researching for 2 days but I couldn't find any optimal solution. (redux normalizer, redux form utils etc.)
Thanks.
This worked for me:
resetAdvancedFilters(){
const fields = ['my','fields','do','reset','properly']
for (var i = 0; i < fields.length; i++) {
this.props.dispatch(change('formName',fields[i],null))
this.props.dispatch(untouch('formName',fields[i]))
}
}
Using the below, it clears the respective multiple fields and also clears the errors if any for those respective fields.
Common function to reset multiple fields.
import {change, untouch} from 'redux-form';
//reset the respective fields's value with the error if any
resetFields = (formName, fieldsObj) => {
Object.keys(fieldsObj).forEach(fieldKey => {
//reset the field's value
this.props.dispatch(change(formName, fieldKey, fieldsObj[fieldKey]));
//reset the field's error
this.props.dispatch(untouch(formName, fieldKey));
});
}
Use the above common function as,
this.resetFields('userDetails', {
firstName: '',
lastName: '',
dateOfBirth: ''
});
Wow, that's some complicated logic. The absolute ideal way would be to use the plugin() API to change values in the reducer when your other field values change. It's a complicated API, because you have total control to mess things up in the reducer, but you have a complicated requirement.
However, dispatching three change() actions like you said you are doing should also work fine. You might want/need to untouch() the fields as well if they have Required validation messages that you don't want to show yet.
It always blocked by my validation method and clear operation is failed.
Can you elaborate on what that means, show a stack trace or console error output?
there.
Let's look at http://redux-form.com/6.0.0-alpha.4/docs/faq/HowToClear.md/
For whole form we can use 4 methods:
A) You can use the plugin() API to teach the redux-form reducer to respond to the action dispatched when your submission succeeds.
B) Simply unmount your form component
C) You can call this.props.resetForm() from inside your form after your submission succeeds.
D) You can dispatch reset() from any connected component
Try to use the dispatch function in meta object with following payload:
meta.dispatch({
type: '##redux-form/CHANGE',
payload: null,
meta: { ...meta, field: name },
})
That should do the trick for any field you want.
I found a better way for this,
We can use initialize action creator of RF.
let resetFields = {
firstName: '',
lastName: '',
dateOfBirth: ''
}
this.props.initialize(resetFields, true); //keepDirty = true;
If the keepDirty parameter is true, the values of currently dirty fields will be retained to avoid overwriting user edits.
clear multiple fields
import {change} from 'redux-form';
const fields = {name: '', address: ''};
const formName = 'myform';
const resetForm = (fields, form) => {
Object.keys(fields).forEach(field => dispatch(change(form, field, fields[field])));
}
resetForm(fields,formName);
If we are talking about entering fields into a form, and then navigating away before submitting, this is the best solution. Place this in your componentDidMount() with the fields you want cleared.
this.props.initialize({
firstName: null,
lastName: null,
bankRoutingNum: null,
newBankType: null
});
For a functional component, you can you change function of RF.
Pass change as props to your component and use it like change(Field_name, value)
For example:
import { reduxForm } from "redux-form";
const Screen = ({
handleSubmit,
change
}) => {
onChange = () => {
change(Field_name, value)
}
return (
// Your code
)}
You can use the clearFields prop or action creator to clear multiple fields.
For example, in this simple form, I use the clearFields prop to clear the firstName and lastName fields.
import React from "react";
import { Field, reduxForm } from "redux-form";
const SimpleForm = (props) => {
const { clearFields } = props;
const clearName = (e) => {
e.preventDefault();
clearFields(false, false, ...["firstName", "lastName"]);
};
return (
<form>
<div>
<label>First Name</label>
<Field name="firstName" component="input" type="text" />
</div>
<div>
<label>Last Name</label>
<Field name="lastName" component="input" type="text" />
</div>
<div>
<label>Occupation</label>
<Field name="occupation" component="input" type="text" />
</div>
<button onClick={clearName}>Clear Name</button>
</form>
);
};
export default reduxForm({
form: "simple"
})(SimpleForm);

Resources