How can I check react-datepicker value with react-hook-form? - react-hooks

I have a register form and use react-hook-form for validation. I want to give an error message if under 15 years old. Is this possible?
here is the picker code
<DatePicker ref={ref} name="birthday" dateFormat="dd/MM/yyyy" disabled={disabled}
selected={startDate || value}
onChange={date => onChangePicker(date)}
maxDate={addDays(new Date()), 1)}
onFocus={() => { focusInput() }}
onBlur={(e) => { blurInput(e) }}
autoComplete="off"
customInput={
<MaskedInput
mask={[/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/]}
/>
}
/>
and here in form
{errors.birthday && <span className="input__error-message birthday-error">Birthday is required</span>}
<Controller
name="birthday"
control={control}
defaultValue={false}
render={({ onChange, value }) => <Calendar label="Birthday" onChange={onChange} value={value} />}
rules={{ required: true }}
register={register}
/>

Yes, this is possible. You just have to use the validate function provided by the rules prop object.
const isOlderThan15Years = date => {...}
rules={{ required: true, validate: date => isOlderThan15Years(date) }}
Check the register section in the documentation for more information.

Related

React Formik props isSubmitted with react-query do not take affect

I have my Formik form below (I'm new with formik). An this is the simple form which have two fields and makes api call using react-query (react -query works well).
But the problem is that formik state isSubmitting do not take effect when click. (Button do not start spinning, but if it is direct true, it works well)
I wonder what i'm missing? Why it has no effect? and How it could be solved?
Api Call:
const createUser = async ({name, password}: Record<string, string>) => {
return await axiosInstance.post(`users/create_user/`, {
name: name,
password: password,
});
};
React-query Mutation:
const createNewUser = useMutation(createUser, {
onSuccess: () => {
queryClient.refetchQueries(["getAllUsers"])
},
onError: (newUser) => {
console.log("onError", newUser);
}
Formik Form:
<Formik
initialValues={{ username: username, password: password }}
validateOnChange={false}
validateOnBlur={false}
onSubmit={(values, {setSubmitting, resetForm }) => {
setSubmitting(true)
createNewUser.mutate({
name: values.username,
password: values.password,
});
setSubmitting(false);
resetForm();
}}
>
{({ values, errors, isSubmitting, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<label
htmlFor="Имя"
className="block text-900 font-medium mb-2"
>
</label>
<Field
type="text"
name="username"
className="w-full mb-3"
as={InputText}
/>
<Field
type="text"
name="password"
className="w-full mb-3"
as={InputText}
/>
<Button
label="save new user"
icon="pi pi-user"
className="w-full"
type="submit"
loading={isSubmitting} // to not take effect? why?
/>
</div>
</form>
Try using setTimeout, to setSubmitting(false) after few seconds not immediately like this:
setTimeout(() => setSubmitting(false), 2000);
Or, check if mutation is successfully executed, then setSubmitting to false
if(createNewUser.status == 'success' || createNewUser.status == 'error') {
setSubmitting(false);
}
Alternatively, without using formik isSubmitting you can use
createNewUser.isLoading
<Button
label="save new user"
icon="pi pi-user"
className="w-full"
type="submit"
loading={createNewUser.isLoading}
/>

Can't get Formik form to work with Material UI

I'm struggeling with making my Formik form work. Here a part of my code:
import { Formik, Form as FormikForm, Field } from "formik";
import {TextField} from "#mui/material";
<Formik
initialValues={{
name: "test",
}}
onSubmit={this.onSubmit}
>
{({ values,handleChange }) => (
<FormikForm>
<Field
component={TextField}
name="name"
label="Name"
fullWidth
></Field>
<Button
color="success"
variant="contained"
type="submit"
onClick={() => {
console.log(values);
}}
>
Erstellen
</Button>
</FormikForm>
)}
</Formik>
It seems like its having trouble connecting the values state to the Field value, since the initialValue isn't displayed in the names field. When i submit, it logs {name: 'test'} to console, no matter what i enter in the input field.
Edit 1:
I also have a very similar form that works. The only difference between the two that i can think of is: the working one is a .jsx while this one is .tsx. Dont know if that has anything to do with it.
According to the Field documentation you need to spread the field and pass it to the component like this:
import { Formik, Form as FormikForm, Field } from "formik";
import { Button, TextField } from "#mui/material";
const MuiTextField = ({ field, form, ...props }) => {
return <TextField {...field} {...props} />;
};
const MyComponent = () => {
return (
<Formik
initialValues={{
name: "test"
}}
onSubmit={(values) => {
console.log(values);
}}
>
{({ values, handleChange }) => (
<FormikForm>
<Field
component={MuiTextField}
name="name"
label="Name"
fullWidth
></Field>
<Button
color="success"
variant="contained"
type="submit"
onClick={() => {
console.log(values);
}}
>
Erstellen
</Button>
</FormikForm>
)}
</Formik>
);
};
export default MyComponent;
You can take a look at this sandbox for a live working example of this solution.
Like Ahmet pointed out, the problem seems to be that the field prop needs to be spread (see his answer). However i found, that using as instead of component works too and doesnt need spreading.
<Field
as={TextField}
name="name"
label="Name"
fullWidth
></Field>

Formik - TextField does not accept numeric value when using react-number-format

I'm using react-number-format library if numeric values is set to TextField it will not show any error after reset the form.
t should accept the numeric values also like other string values.
Here is my code you are reproduce error from this.
Formik Form
<Formik
enableReinitialize
initialValues={{
area: "",
password: "",
}}
validate={(values) => {
const errors: Partial<Values> = {};
if (!values.area) {
errors.area = "Required area";
}
return errors;
}}
onSubmit={(values, { setSubmitting, resetForm }) => {
setTimeout(() => {
setSubmitting(false);
console.log(JSON.stringify(values, null, 2));
resetForm();
}, 500);
}}
>
{({ submitForm, isSubmitting }) => (
<Form>
<Field
component={TextField}
name="area"
label="area"
InputProps={{
inputComponent: NumberField,
}}
/>
<br />
<Field
component={TextField}
type="password"
label="Password"
name="password"
/>
{isSubmitting && <LinearProgress />}
<br />
<Button
variant="contained"
color="primary"
disabled={isSubmitting}
onClick={submitForm}
>
Submit
</Button>
</Form>
)}
</Formik>
NumberField
import NumberFormat from "react-number-format";
interface NumberFieldProps {
name: string;
inputRef: (instance: NumberFormat<any> | null) => void;
onChange: (event: {
target: { name: string; value: number | undefined };
}) => void;
}
export default function NumberField(props: NumberFieldProps) {
const { inputRef, onChange, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={(values) => {
onChange({
target: {
name: other.name,
value: values.floatValue,
},
});
}}
/>
);
}
Click on submit button and you will see Required area error, Set value in area field and submit form, it submit the form and reset it after that click again on submit button you will see it will not give you Required area error. I don't know why this happen.
Now change value: values.floatValue to value: values.value in NumberField file and everything work well. I don't know why it is not working when I set floatValue I need numeric values not string.

Using formik how to add values to formik values when TypeAhead field is selected

I'm using formik for my form with a TypeAhead field that allow the user to select array of objects. I want to update Formik values with the array of objects when selected. How do I update formik values when the value has been selected? I have tried setFieldValue, but how can it be done in this manner?
<Formik
initialValues={{
driverList: [
{
id:'',
firstName: '',
lastName: '',
smsPhone: '',
email: ''
}
],
}}
validationSchema={Yup.object({
activateAccount: Yup.boolean()
.required('Required')
.oneOf([true], 'Account must be activated.'),
})}
onSubmit={values => {
alert(JSON.stringify(values, null, 4));
console.log(values)
}}
>
{({handleSubmit, setFieldValue, values}) => (
<div>
<Card className="callSupportCard">
<Card.Body>
<Card.Title className="callSupportTitle">
Account Activation
</Card.Title>
<Card.Subtitle className="callSupportCheckBox">
The customer has started to setup up his/her account but could not finish.
</Card.Subtitle >
<p className="callQuestion">{" "}<b>Step 1:{" "}</b> Ask for their first and last name.</p>
<Form onSubmit={handleSubmit}>
<Typeahead className="driverInput" id="driversInfo"
labelKey={(option) => `${option.firstName} ${option.lastName}, ${option.smsPhone}, ${option.email}`}
onChange={setSelected}
options={options}
selected={selected}
placeholder="Choose an driver..."
/>
Solution was:
<Typeahead
labelKey={(option) => `${option.firstName} ${option.lastName}, ${option.phone}, ${option.email}`}
onChange={(selected) => {
if(selected.length > 0)
{setFieldValue('firstName', selected[0].firstName);
setFieldValue('lastName', selected[0].lastName);
setFieldValue('smsPhone', selected[0].phone);
setFieldValue('email', selected[0].email);
setFieldValue('suspended', selected[0].suspended);
}}}
options={options}
placeholder="Choose an person..."
/>

How to add password matching validation in vuetify?

I am trying to create a profile form which have two fields
password and rePassword. basically, Both of them should be the same.
I tried using different codes found on the web and different approaches. Some of them worked but. It did'nt actually fit in with the code.
Here is the piece of code:
Profile.vue:
<v-layout>
<v-flex xs12 sm6>
<v-text-field
v-model="password"
:append-icon="show ? 'visibility' : 'visibility_off'"
:rules="[rules.required, rules.min]"
:type="show ? 'text' : 'password'"
name="password"
label="Enter Password"
hint="At least 8 characters"
counter
#click:append="show = !show"
></v-text-field>
</v-flex>
<v-flex xs12 sm6>
<v-text-field
v-model="rePassword"
:append-icon="show1 ? 'visibility' : 'visibility_off'"
:rules="[rules.required, rules.min]"
:type="show1 ? 'text' : 'password'"
name="input-10-1"
label="Re-enter Password"
hint="At least 8 characters"
counter
#click:append="show1 = !show1"
></v-text-field>
</v-flex>
</v-layout>
This is how the script looks like:
Profile.vue (script):
data() {
return {
show: false,
show1: false,
password: 'Password',
rePassword: 'Password',
rules: {
required: value => !!value || 'Required.',
min: v => v.length >= 8 || 'Min 8 characters',
emailMatch: () => ('The email and password you entered don\'t match')
},
emailRules: [
v => !!v || 'E-mail is required',
v => /.+#.+/.test(v) || 'E-mail must be valid'
],
date: new Date().toISOString().substr(0, 10),
menu: false,
items: ['male', 'female'],
address: '',
title: "Image Upload",
dialog: false,
imageName: '',
imageUrl: '',
imageFile: ''
}
},
methods: {
pickFile() {
this.$refs.image.click()
},
onFilePicked(e) {
const files = e.target.files
if(files[0] !== undefined) {
this.imageName = files[0].name
if(this.imageName.lastIndexOf('.') <= 0) {
return
}
const fr = new FileReader ()
fr.readAsDataURL(files[0])
fr.addEventListener('load', () => {
this.imageUrl = fr.result
this.imageFile = files[0] // this is an image file that can be sent to server...
})
} else {
this.imageName = ''
this.imageFile = ''
this.imageUrl = ''
}
},
}
,
validate() {
if (this.$refs.form.validate()) {
this.snackbar = true
}
},
reset() {
this.$refs.form.reset()
}
How do I add a password matching feature in the validation using vuetify.
Thanks
You can define custom rules:
computed: {
passwordConfirmationRule() {
return () => (this.password === this.rePassword) || 'Password must match'
}
}
and use it
<v-flex xs12 sm6>
<v-text-field
v-model="rePassword"
:append-icon="show1 ? 'visibility' : 'visibility_off'"
:rules="[rules.required, rules.min, passwordConfirmationRule]"
:type="show1 ? 'text' : 'password'"
name="input-10-1"
label="Re-enter Password"
hint="At least 8 characters"
counter
#click:append="show1 = !show1"
/>
</v-flex>
very easiest way is
using v-model (password and confirm_password), no need to use computation
Rule
:rules="[v => !!v || 'field is required']"
Or
:rules="[(password!="") || 'field is required']"
in password
<v-text-field label="Password*" v-model="password" type="password" required :rules="[v => !!v || 'field is required']"></v-text-field>
confirm password field
Rule
:rules="[(password === confirm_password) || 'Password must match']"
code:
<v-text-field label="Confirm Password*" v-model="confirm_password" type="password" required :rules="[(password === confirm_password) || 'Password must match']"></v-text-field>
VeeValidate is great for form validation but in my opinion is overkill for resolving this question when it can be achieved in Vuetify alone.
Following on from #ittus answer, you need to remove the arrow function in passwordConfirmationRule to access this:
return this.password === this.rePassword || "Password must match";
See this codesandbox working example (also now using Vuetify 2.x)
Very simply using Vee-validate:
<div id="app">
<v-app id="inspire">
<form>
<v-text-field
ref="password"
type="password"
v-model="pass"
v-validate="'required'"
:error-messages="errors.collect('pass')"
label="Pass"
data-vv-name="pass"
required
></v-text-field>
<v-text-field
v-model="pass2"
type="password"
v-validate="'required|confirmed:password'"
:error-messages="errors.collect('pass2')"
label="Pass 2"
data-vv-name="pass"
required
></v-text-field>
<v-btn #click="submit">submit</v-btn>
<v-btn #click="clear">clear</v-btn>
</form>
</v-app>
</div>
Vue.use(VeeValidate)
new Vue({
el: '#app',
$_veeValidate: {
validator: 'new'
},
data: () => ({
pass: '',
pass2: "",
}),
methods: {
submit () {
this.$validator.validateAll()
.then(result => {
console.log(result)
})
},
clear () {
this.pass = ''
this.pass2 = ''
}
}
})
Remember to install vee-validate first and restart your local-server.
link to codepen
link to docs
This works right for me!
// template
<v-text-field v-model="password" label="password"></v-text-field>
<v-text-field v-model="confirmPassword" :rules="confirmPasswordRules" label="confirmPassword"></v-text-field>
// script
computed: {
confirmPasswordRules() {
const rules = [(this.password === this.confirmPassword) || "Password must match."];
return rules;
},
}
This is not a clean solution definitely, but works fine.
<v-text-field
type="password"
v-model="password"
:rules="passwordRules"
required
></v-text-field>
<v-text-field
type="password"
v-model="passwordConfirm"
:rules="passwordConfirmRules"
required
></v-text-field>
let globalPassword = "";
watch: {
password() {
globalPassword = this.password;
},
},
passwordConfirmRules = [
(v) => v === globalPassword || "Password must match",
];

Resources