I have a redux form with two input fields and one textarea. The input field values are updated for each keypress and the validate function gets the right values. But the textarea keychanges are not reflected in the validate function. Any help ? The entire code is:
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { createPost } from '../actions/index';
const renderInput = ({ 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 renderTextArea = ({textarea, meta: { touched, error, warning }}) => (
<div>
<label>Content</label>
<div>
<span>{textarea}</span>
<textarea {...textarea} placeholder="Content" rows="10" cols="40"></textarea>
{touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))}
</div>
</div>
);
class PostsNew extends Component {
render() {
const { handleSubmit, title, categories, content } = this.props;
return (
<form onSubmit={handleSubmit(createPost)}>
<Field name="title" component={renderInput} label="Title" type="text" {...title} />
<Field name="categories" component={renderInput} label="Categories" type="text" {...categories} />
<Field name="content" component={renderTextArea} {...content} />
<button type="submit">Submit</button>
</form>
);
}
}
const validate = values => {
const errors = {}
if (!values.title) {
errors.title = 'Required';
}
if (!values.categories) {
errors.categories = 'Required';
}
console.log("######", values);
console.log("####", values.content);
if (!values.content) {
errors.content = 'Content cannot be empty';
} else if (values.content.length < 3) {
errors.content = 'Content should be more than 3 characters';
}
return errors;
}
export default reduxForm({
form: 'NewPostForm',
validate
})(PostsNew);
The console.log function calls in the validate function return the right value for the two input fields on each keypress but not the value for the content.
If I replace the textarea Field line, with the following line, the content value is correctly logged on each keypress in the text area (but I cannot validate).
Updated line: <Field name="content" component={textarea} {...content} /> that results in the console.log to reflect keypress.
Looks like a classic search-and-replace error. :-)
const renderTextArea = ({textarea, meta: { touched, error, warning }})
^^^^^^^^
There's your problem. The key is input. Try:
const renderTextArea = ({input, meta: { touched, error, warning }}) => (
<div>
<label>Content</label>
<div>
<textarea {...input} placeholder="Content" rows="10" cols="40"/>
{touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))}
</div>
</div>
);
Related
Is it possible to substitute the value from useState with the one coming from input?
Or is there a way to do this using dispatch?
I have tried many ways, but none of them work.
const renderInput = ({
input,
label,
type,
meta: { asyncValidating, touched, error },
}) => {
const [value, setValue] = useState('default state');
const onChange = event => {
setValue(event.target.value);
// + some logic here
};
return (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} value={value} onChange={onChange} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
};
const SelectingFormValuesForm = props => {
const { type, handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<label>Name</label>
<div>
<Field
name="name"
component={renderInput}
type="text"
placeholder="Dish name..."
/>
</div>
</div>
</form>
);
};
SelectingFormValuesForm = reduxForm({
form: 'selectingFormValues',
validate,
asyncValidate,
// asyncBlurFields: ['name'],
})(SelectingFormValuesForm);
export default SelectingFormValuesForm;
This way, unfortunately, the value sent to the submit remains empty.
I have a form with a button to submit it, and I also use the same button to route to another page.
the button work in routing and when I remove the Navlink tag it submits the form. but it does not submit it when the Navlink tags are there and does not show the validation error msgs as well it just route the page.
any help on how to get the two actions to work?
here is my code
import react, { Component, useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faEyeSlash, faEye } from "#fortawesome/free-solid-svg-icons";
import './sign.css';
const Sign = () => {
//state to be sent to backend
const intialValues = { firstname: "", lastname: "", mobile: "", email: "", password: "", cpassword: "" };
const [formValues, setFormValues] = useState(intialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const handleChange = (e) => {
console.log(e.target.value);
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
}
const handleSubmit = (err) => {
err.preventDefault();
setFormErrors(validate(formValues));
setIsSubmit(true);
}
useEffect(() => {
if (Object.keys(formErrors).length === 0 && isSubmit) {
console.log(formValues);
}
}, [formErrors])
const validate = (values) => {
const errors = {};
if (!values.firstname) {
errors.firstname = 'firstname is required!';
}
if (!values.lastname) {
errors.lastname = 'lastname is required!';
}
return errors;
}
return (
<div className='signup'>
<form onSubmit={handleSubmit} >
<div className="container">
<h1>Sign Up</h1>
<div className="name">
<div>
<input
type="text"
placeholder="First name"
name="firstname"
id='firstName'
value={formValues.firstname}
onChange={handleChange}
/>
</div>
<div>
<input
type="text"
placeholder="Last name"
name="lastname"
value={formValues.lastname}
onChange={handleChange}
/>
</div>
</div>
<p className='errorMsg'>{formErrors.firstname}</p>
<p className='errorMsg'>{formErrors.lastname}</p>
<br />
<div className="clearfix">
<NavLink to='/profileclient'>
<button type="submit" className="signupbtn">Sign Up</button>
</NavLink>
</div>
</div>
</form>
</div>
)
}
export default Sign;
then I tried to use useNavigate so I modified these lines, and it does navigate to the other page but in the console, it gives me a warning "Form submission canceled because the form is not connected", it does not log the state objects.
const navigate = useNavigate();
<div className="clearfix">
<button type="submit" className="signupbtn" onClick={() => { navigate('/profileclient') }}>Sign Up</button>
</div>
I discovered the solution.
the main problem was that I tried to trigger functions at the same time by clicking on a button, while the submission needs time first before the navigation could happen, this is why it did not submit the form but only navigated. in order to solve the ambiguous behavior, I have put the navigation function call in if statement to make sure the submission is done or not before the navigation is executed. I have also changed the place where I call the navigate function, rather than calling it on the button onClick attribute, I placed it in the useEffect where I check if there are no errors you can submit the form and after submitting it you can navigate to another page.
this is the part of my code that made it work well.
if (isSubmit) {
return (navigate('/profileclient'));
}
and here is my full code
import react, { Component, useState, useEffect } from 'react';
import { NavLink, useNavigate } from 'react-router-dom';
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faEyeSlash, faEye } from "#fortawesome/free-solid-svg-icons";
import './sign.css';
const SignC = () => {
const navigate = useNavigate();
//state to be sent to backend
const intialValues = { firstname: "", lastname: "", mobile: "", email: "", password: "", cpassword: "" };
const [formValues, setFormValues] = useState(intialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const [passwordShown, setPasswordShowen] = useState(false);
const [cPasswordShown, setCPasswordShowen] = useState(false);
const [eyeShowen, setEyeShowen] = useState(false);
const [cEyeShowen, setCEyeShowen] = useState(false);
const handleChange = (e) => {
console.log(e.target.value);
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
}
const handleSubmit = (err) => {
err.preventDefault();
setFormErrors(validate(formValues));
setIsSubmit(true);
}
useEffect(() => {
if (Object.keys(formErrors).length === 0 && isSubmit) {
console.log(formValues);
if (isSubmit) {
return (navigate('/profileclient'));
}
}
}, [formErrors])
const validate = (values) => {
const errors = {};
const regex = /^[^\s#]+#[^\s#]+\.[^\s#]{2,}$/i;
if (!values.firstname) {
errors.firstname = 'firstname is required!';
}
if (!values.lastname) {
errors.lastname = 'lastname is required!';
}
if (!values.mobile) {
errors.mobile = 'mobile is required!';
}
if (!values.email) {
errors.email = 'email is required!';
} else if (!regex.test(values.email)) {
errors.email = 'this is not a valid email format!'
}
if (!values.password) {
errors.password = 'password is required!';
} else if (values.password.length < 4) {
errors.password = 'password must be more than 4 characters';
} else if (values.password.length > 10) {
errors.password = 'password must not exceeds 10 characters';
}
if (!values.cpassword) {
errors.cpassword = 'password confirmation is required!';
} else if (values.cpassword != values.password) {
errors.cpassword = 'confirmation password does not match!';
}
return errors;
}
const togglePassword = () => {
setPasswordShowen(!passwordShown);
toggleEye();
}
const toggleCPassword = () => {
setCPasswordShowen(!cPasswordShown);
toggleCEye();
}
const toggleEye = () => {
setEyeShowen(!eyeShowen);
}
const toggleCEye = () => {
setCEyeShowen(!cEyeShowen);
}
return (
<div className='signup'>
<form onSubmit={handleSubmit} >
<div className="container">
<h1>Sign Up</h1>
<div className="name">
<div>
<input
type="text"
placeholder="First name"
name="firstname"
id='firstName'
value={formValues.firstname}
onChange={handleChange}
/>
</div>
<div>
<input
type="text"
placeholder="Last name"
name="lastname"
value={formValues.lastname}
onChange={handleChange}
/>
</div>
</div>
<p className='errorMsg'>{formErrors.firstname}</p>
<p className='errorMsg'>{formErrors.lastname}</p>
<br />
<div>
<input
type="text"
placeholder="Business mobile number"
name="mobile"
value={formValues.mobile}
onChange={handleChange}
/>
<p className='errorMsg'>{formErrors.mobile}</p>
<br />
<input
type="text"
placeholder="Email Adress"
name="email"
value={formValues.email}
onChange={handleChange}
/>
<p className='errorMsg'>{formErrors.email}</p>
<br />
<div className="password">
<input
type={passwordShown ? 'text' : 'password'}
placeholder="Password"
name="password"
id='password'
value={formValues.password}
onChange={handleChange}
/>
<FontAwesomeIcon
icon={eyeShowen ? faEye : faEyeSlash}
id='togglePassword'
onClick={togglePassword}
/>
<p className='errorMsg'>{formErrors.password}</p>
<br />
<input
type={cPasswordShown ? 'text' : 'password'}
placeholder="Confirm Password"
name="cpassword"
id='Cpassword'
value={formValues.cpassword}
onChange={handleChange}
/>
<FontAwesomeIcon
icon={cEyeShowen ? faEye : faEyeSlash}
id='toggleCPassword'
onClick={toggleCPassword}
/>
<p className='errorMsg'>{formErrors.cpassword}</p>
</div>
</div>
<br />
<div className="checkbox">
<label>
<input type="checkbox" className="check" />i’ve read and agree with <a href="url" >Terms of service</a>
</label>
</div>
<div className="clearfix">
<button type="submit" className="signupbtn">Sign Up</button>
</div>
</div>
</form >
</div >
)
}
export default SignC;
I am trying to make a button which submit the form and then navigate
to another page.
The important distinction here is the then. When you wrap a link around a button both elements are clicked.
<NavLink to='/profileclient'>
<button type="submit" className="signupbtn">Sign Up</button>
</NavLink>
The link tries to navigate at the same time the form is processing its onSubmit handler.
If you want to conditionally navigate at the after submitting the form then you need to move the navigation logic there. Use the useHistory or useNavigate (depending on react-router-dom version, v5 the former, v6 the latter). There's also no need to store an isSubmit state, you can simply check the validation result in the submit handler and conditionally set the form errors or navigate to the next page.
Example:
const Sign = () => {
const navigate = useNavigate();
...
const handleSubmit = (event) => {
event.preventDefault();
const errors = validate(formValues);
if (Object.values(errors).length) {
setFormErrors(errors);
} else {
navigate("/profileclient");
}
};
const validate = (values) => {
const errors = {};
...
return errors;
};
return (
<div className="signup">
<form onSubmit={handleSubmit}>
<div className="container">
<h1>Sign Up</h1>
...
<div className="clearfix">
<button type="submit" className="signupbtn">
Sign Up
</button>
</div>
</div>
</form>
</div>
);
};
I have a question regarding react-final form error message when using record-level validation. I have the following field present within FormFilterFields component.
Field Names:
export const fieldNames = {
password: "field.password"
};
Field:
<Field name={fieldNames.password}>
{({ input, meta }) => {
return (
<div className={styles.textInputContainer}>
<TextInput
type={"password"}
label={"password"}
value={input.value}
onChange={(event)=> {
input.onChange(event)
}}
error={meta.error}
/>
</div>
);
}}
</Field>
Form:
<Form
onSubmit={() => {}}
initialValues={this.props.formValues}
decorators={[formFilterFieldsDecorator]}
validate={values => {
const valuesObject: any = values
const validationErrors = { errors: {} };
if (valuesObject && valuesObject.field.password && valuesObject.field.password.length < 6){
validationErrors.errors.field.password = "password is too short"; //errors.field.password is undefined here
}
return validationErrors;
}}
render={({
handleSubmit,
submitting,
pristine,
values,
form,
invalid
}) => (
<form onSubmit={handleSubmit}>
<FormFilterFields
form={form}
onSubmit={event => this.props.onHandleSubmit(event, values)}
onReset={event => this.props.onHandleReset(event, form)}
/>
<pre>{JSON.stringify(values)}</pre>
</form>
)}
/>
So essentially how would you set the error message for a field name like so:
"field.password"
I have looked at some of the examples but no dice.The alternative would be field level validation which is my last resort as part of my solution.
Any help would be much appreciated
Thanks
Did you try
validationErrors.errors['field.password'] = "password is too short";
Edit
Try this
if (!values.field || !values.field.password) {
errors.field = {password: 'Required'};
}
I have an API which with an object and encapsulates errors related with each inputField within it, for example:
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"firstname": [
"Invalid firstname",
"Firstname must be at least 2 characters long"
],
"companyname": ["company name is a required field"]
}
}
based on the errors, I need to display the error right below the input element, for which the code looks like this:
class RegistrationComponent extends Component {
onSubmit = (formProps) => {
this.props.signup(formProps, () => {
this.props.history.push('/signup/complete');
});
};
render() {
const {handleSubmit} = this.props;
return (
<div>
<form
className='form-signup'
onSubmit={handleSubmit(this.onSubmit)}
>
<Field name='companyname' type='text' component={inputField} className='form-control' validate={validation.required} />
<Field name='firstname' type='text' component={inputField} className='form-control' validate={validation.required} />
<Translate
content='button.register'
className='btn btn-primary btn-form'
type='submit'
component='button'
/>
</form>
</div>
);}}
The inputField:
export const inputField = ({
input,
label,
type,
className,
id,
placeholder,
meta: { error, touched },
disabled
}) => {
return (
<div>
{label ? (
<label>
<strong>{label}</strong>
</label>
) : null}
<Translate
{...input}
type={type}
color={"white"}
className={className}
id={id}
disabled={disabled}
component="input"
attributes={{ placeholder: placeholder }}
/>
<InputFieldError
touched={touched}
error={<Translate content={error} />}
/>
</div>
);
};
and finally;
import React, { Component } from "react";
class InputFieldError extends Component {
render() {
return <font color="red">{this.props.touched && this.props.error}</font>;
}
}
export default InputFieldError;
If I validate simply with validate={validation.required} the error property is attached to the correct input field and I can render the error div using InputFieldError right below it.
I am mapping all the errors back from the API response on to props like this:
function mapStateToProps(state) {
return { errorMessage: state.errors, countries: state.countries };
}
which means I can print every error that I encounter at any place like this:
{this.props.errorMessage
? displayServerErrors(this.props.errorMessage)
: null}
rendering all at same place by simply going through each property on errorMessage is easy.
Now when I try to assign the errors back from the API ("errors": {"firstname": []} is linked with Field name="firstname" and so on...), I cannot find a way to attach the error in "firstname" property to the correct InputFieldError component in Field name="firstname"
I hope the question is clear enough, to summarise it I am trying to render error I got from API to their respective input element.
Try to pass errors.firstname to field inner component like this
<Field name='companyname' type='text' component={<inputField apiErrors={this.props.errorMessage.errors.firstname || null} {...props}/>} className='form-control' validate={validation.required} />
and then your inputField will be:
export const inputField = ({
input,
label,
type,
className,
id,
placeholder,
meta: { error, touched },
apiErrors,
disabled
}) => {
return (
<div>
{label ? (
<label>
<strong>{label}</strong>
</label>
) : null}
<Translate
{...input}
type={type}
color={"white"}
className={className}
id={id}
disabled={disabled}
component="input"
attributes={{ placeholder: placeholder }}
/>
<InputFieldError
touched={touched}
error={apiErrors ? apiErrors : <Translate content={error} />}
/>
</div>
);
};
and:
class InputFieldError extends Component {
render() {
const errors = this.props.error
let errorContent;
if (Array.isArray(errors)) {
errorContent = '<ul>'
errors.map(error, idx => errorContent+= <li key={idx}><Translate content={error}/></li>)
errorContent += '</ul>'
} else {
errorContent = errors
}
return <font color="red">{this.props.touched && errorContent}</font>;
}
}
You can also add the main error at the top of your form
class RegistrationComponent extends Component {
onSubmit = (formProps) => {
this.props.signup(formProps, () => {
this.props.history.push('/signup/complete');
});
};
render() {
const {handleSubmit} = this.props;
return (
<div>
{this.props.errorMessage.message &&
<Alert>
<Translate content={this.props.errorMessage.message}/>
</Alert>
}
<form
className='form-signup'
onSubmit={handleSubmit(this.onSubmit)}
>
...
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.