I am making a simple react form with a react-hook-form and yup as the validator. If the user's input is successfully validated, I want to show some feedback to the user, like a green outline. The problem is that I only want to show the green outline when my input has been validated, not every time the component is rendered. How can I do this? Component:
const schema = yup.object().shape({
email: yup
.string()
.required("This field is required"),
password: yup.string().required("This field is required"),
});
export const Form = () => {
const {
register,
handleSubmit,
getValues,
formState: { errors },
} = useForm({
mode: "onBlur",
resolver: yupResolver(schema),
});
return (
<form noValidate onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="email">Email</label>
<input
id="email"
{...register("email")}
/>
{errors.email ? errors?.email.message : null}
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
{...register("password")}
/>
{errors.password ? errors?.password.message : null}
<button
type="submit"
>
Submit
</button>
</form>
);
};
You can implement what you want with the touched property. Using the touched property for this kind of scenario is very common. Here's what you can do:
From the useForm hook, you can also extract the touchedFields
const {
register,
handleSubmit,
getValues,
formState: { errors, touchedFields }
} = useForm({
mode: "onBlur",
resolver: yupResolver(schema)
});
The touchedFields properties stores the touched fields. A field is touched the first time the user leaves the focus from that field. So, then you can conditionally show a message about a field if it is touched and it has no errors like this:
{touchedFields.email && !errors.email ? <div>Email ok</div> : null}
You can try this sandbox. I hope you can get the idea from it.
Related
I have an onChange function onNameChange that contains a valid variable that should match the yup validation of the name field. The problem is that the valid variable only seems to be correct after submitting the form, not on changing the name field; I want this to be valid before having to submit.
How can I get the value to be correct on changing the name field rather than submitting? Note that I found a similar post but that uses Formik, which is not what I want to use: Formik + Yup: How to immediately validate form before submit?
The Yup settings:
const schema = Yup.object().shape({
name: Yup.string()
.required("Required")
.min(3, "Enter at least 3 characters")
});
const {
register,
handleSubmit,
setError,
formState: { errors },
trigger
} = useForm({
resolver: yupResolver(schema)
// mode: "onTouched",
// reValidateMode: "onChange"
});
The name changing function:
const onNameChange = async ({ target: { value } }) => {
const valid = await trigger("name");
console.log("valid", valid, "value", value);
if (!valid) {
// #todo: bug here? valid only correct after submitting
return;
}
getPokemon(value);
setShowPokemon(false);
};
The demo form:
<form onSubmit={handleSubmit(onSubmit /*, onError*/)}>
<input
{...register("name", { required: true })}
name="name"
placeholder="Enter a pokemon"
onChange={onNameChange}
/>
<button type="submit" onClick={onSubmit}>
Show Pokemon
</button>
{errors.name && <p>{errors.name.message}</p>}
</form>
I've made a live demo on codesandbox that should be helpful:
https://codesandbox.io/s/react-playground-forked-odwi2?file=/Pokemon.js
Thanks
The problem is that you aren't updating the RHF state after changing your name <input />, because you are overriding the onChange prop, which is returned from {...register('name')}.
So basically you have to options here:
use setValue to update the RHF state value for name inside your onNameChange callback
use <Controller /> component
You can read about it in this discussion on GitHub.
This how it would be implemented for the second option using <Controller />:
<form onSubmit={handleSubmit(onSubmit /*, onError*/)}>
<Controller
name="name"
control={control}
defaultValue=""
render={({ field: { value, onChange, ...field } }) => (
<input
{...field}
onChange={({ target: { value } }) => {
onChange(value);
onNameChange(value);
}}
placeholder="Enter a pokemon"
/>
)}
/>
<button type="submit" onClick={onSubmit}>
Show Pokemon
</button>
{errors.name && <p>{errors.name.message}</p>}
</form>
I have a problem in the input validation. The validation works when I submit and a error message appears, but when I press the first key on the keyboard nothing appears in the textarea and the error message disappears; after that, I can write normally. Its an inconvenience and I don't know why its happening. I am using the TextArea from Material UI. The code snippet of a login form is below.
const schema = yup.object().shape({
username: yup.string().matches(/^[a-z0-9]+$/, 'Must be all lower-case letters.').required(),
password: yup.string().required(),
})
const Login = props => {
const [formValues, setFormValues] = React.useState({
username: "",
password: ""
});
const { register,errors, handleSubmit } = useForm({
resolver: yupResolver(schema),
mode: 'onSubmit',
});
const onSubmit = async (data, e) => {
e.preventDefault()
const isValid = await schema.isValid(data)
if(isValid){
console.log(data);
}
}
return (
<Container component="main" maxWidth="xs">
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
autoFocus
required
fullWidth
id="username"
label="Username"
name="username"
value={formValues.username}
inputRef={register}
helperText = {errors.username?.message}
/>
<TextField
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
value={formValues.password}
inputRef={register}
helperText = {errors.password?.message}
/>
<Button
type="submit"
fullWidth
className={classes.submit}
>
Login
</Button>
</form>
</div>
</Container>
);
}
I worked around this (in React Native) by using reValidateMode:"onBlur" in the useForm() options.
This way it doesn't retry the validation until the user leaves the input.
I have a weird requirement. I need to do a redux-form validation client side as well as on the server side. I am able to do it on the client side but not sure how can I do for both client and server side. Checked redux-form documentation where it is done either client or server but not for both at once.
Here is the Code
import React from 'react'
import { Field, reduxForm } from 'redux-form'
const validate = values => {
const errors = {}
if (!values.username) {
errors.username = 'Required'
} else if (values.username.length > 15) {
errors.username = 'Must be 15 characters or less'
}
if (!values.email) {
errors.email = 'Required'
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address'
}
if (!values.age) {
errors.age = 'Required'
} else if (isNaN(Number(values.age))) {
errors.age = 'Must be a number'
} else if (Number(values.age) < 18) {
errors.age = 'Sorry, you must be at least 18 years old'
}
return errors
}
const renderField = ({
input,
label,
type,
meta: { touched, error, warning }
}) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched &&
((error && <span>{error}</span>) ||
(warning && <span>{warning}</span>))}
</div>
</div>
)
const SyncValidationForm = props => {
const { handleSubmit, pristine, reset, submitting } = props
return (
<form onSubmit={handleSubmit}>
<Field
name="username"
type="text"
component={renderField}
label="Username"
/>
<Field name="email" type="email" component={renderField} label="Email" />
<Field name="age" type="number" component={renderField} label="Age" />
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
)
}
export default reduxForm({
form: 'syncValidation',
validate
})(SyncValidationForm)
Now onSubmit I have to do an API request and show the errors coming from the server for each field.
Can anyone explain me how can I add sever side validation while I keep client side validation also working?
Thanks in advance.
Well, you need to write server-side code for this. It depends on what language you want to use, but I guess node.js is good to achieve this. So for validating data on the server you should create a nodejs server, and pass your data (that validated already on client-side) then in node.js server validate data again however you want.
So in summary, you should start node.js which is simple because both react/node.js are almost the same.
Hope it helps you
Redux-form has a function you can use to throw submissionErrors inside your onSubmit function. https://redux-form.com/8.2.2/docs/api/submissionerror.md/
This can be done after an asynchronous call.
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 am trying to use redux-form to generate a quiz form. My data source for an individual redux-form field component comes from an array - questions in my case. Everything works as expected except validation. Any thoughts how this can be fixed?
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { Input, Button } from 'reactstrap';
const validate = values => {
const errors = {};
if (!values.question) { // this is just an example of what I am trying to do, validation does not work
errors.question = 'Required';
} else if (values.question.length < 15) {
errors.question = 'Must be 15 characters or more';
}
return errors;
};
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<Input {...input} type={type} />
{touched && (error && <span>{error}</span>)}
</div>
</div>
);
const renderQuestions = questions => {
return questions.map(question => {
return (
<Field key={question.id} name={question.prompt} type="textarea" component={renderField} label={question.prompt} />
);
});
};
const QuizStepForm = props => {
const { handleSubmit, pristine, reset, submitting, questions } = props;
return (
<form onSubmit={handleSubmit}>
<Field name="username" type="textarea" component={renderField} label="username" />
{renderQuestions(questions)}
<div>
<br />
<Button color="primary" style={{ margin: '10px' }} type="submit" disabled={submitting}>
Submit
</Button>
<Button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</Button>
</div>
</form>
);
};
export default reduxForm({
form: 'quizStepForm',
validate
})(QuizStepForm);
Your validation function assumes there is one field named "question." But your code creates a set of fields whose name is set by {question.prompt}. If you stick with this implementation, your validation code will need to know about all the question.prompt array values and check values[question.prompt] for each one, then set errors[question.prompt] for any failures. That would probably work, though it seems like a suboptimal design.
This might be a good use case for a FieldArray. In FieldArrays, the validation function is called for you on each field; your validation code doesn't have to know the names of all the fields.