Using onBlur with redux-form - javascript

I am using redux-form 7.0.4, it its working fine other than the following issue.
New user can enter details and existing user can edit details on the same form. I am having issue when existing user edit details and remove data from form field. I want to add check onBlur. If user empty form field and there is blur event I need to fill the initial data inside field.
Restore the initial/server value on blur if field is empty.
Form Field:
<Field
component={InputField}
type='text'
name='registeredName'
className='form-control'
required
placeholder='Registered Business Name*'
/>
InputField Component:
const InputField = ({
input,
type,
placeholder,
meta: { touched, error, initial }
}) => {
const onChange = (e) => {
const val = e.target.value;
if (type === 'phoneNumber') {
if (!/^\d+$/.test(val) || String(val).length > 10) {
return;
}
}
input.onChange(e);
};
return (
<div className='form-group'>
<input
{...input}
onChange={onChange}
type={type}
className='form-control'
placeholder={placeholder}
value={input.value }
onBlur={e => {input.value = (!e.target.value) ? initial : e.target.value;}}
/>
{touched &&
((error &&
<div className='error text-danger'>
{error}
</div>
))}
</div>
);
};
/* eslint-enable */
export default InputField;

You need to set onChange and onBlur in the Field component like this:
<Field
component={InputField}
type='text'
name='registeredName'
className='form-control'
required
placeholder='Registered Business Name*'
onChange={(event) => {
...your onChange function here...
}}
onBlur={(event) => {
...Your onBlur function here...
}}
/>
I'd recommend having a good read through the docs for the <Field /> component.
Hope that's helpful

Related

React Context Updates Not Firing During OnSubmit Functions?

I am using the Context API to add user details from a form to a global state. When I submit the form, the state is always "one step behind" - essentially, a double click is required to get the desired result.
Basic recreation of the code (have removed irrelevant bits and some imports):
import { UserProvider, UserContext } from "../../StateContext"
export default function SignUp() {
const user = useContext(UserContext)
const history = useHistory()
const handleSubmit = (e) => {
e.preventDefault()
user.setName(userDetails.name)
//logging out the user object here will result in the previous values shown
}
const [userDetails, setUserDetails] = useState({
name: null,
age: null,
})
return (
<>
<form onSubmit={handleSubmit}>
<div className="form-vertical-batch">
<FormControl>
<Input
type="text"
placeholder="Your Name"
required={true}
onChange={(e) =>
setUserDetails({ ...userDetails, name: e.target.value })
}
></Input>
<FormHelperText>
Put the name you're best known by online - either a nickname,
brand name, or your real name.
</FormHelperText>
</FormControl>
<FormControl>
<TextField
type="number"
inputProps={{ min: 18, max: 99 }}
onChange={(e) =>
setUserDetails({ ...userDetails, age: e.target.value })
}
/>
<FormHelperText>
You currently must be at least 18 years old to use the platform.
</FormHelperText>
</FormControl>
</div>
</div>
<input
id="formButton"
className="btn sign-up-button"
type="submit"
placeholder="Send message"
/>
</form>
</>
)
}
To clarify the issue here - if I submit with a name as "Reikon" and log our the user object, the first time it will return as null, and then the second time it will return "Reikon" as expected.

How to give validation in multi step form using react

I am working on a scenario where I have to do a multi-step form which I have already done, as well as the validation part. I am using react-hook-form for validation.
I have multi-step form:
in the first form I have several fields and one radio button
by default radio button is ticked on for auto generated pass so in this case I have nothing to do
the second one is let me create a password so in this case one input field will be show and the user will create the password
Issue
In my final form I am doing the validation like below:
{
fields: ["uname", "email", "password"], //to support multiple fields form
component: (register, errors, defaultValues) => (
<Form1
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
},
So to validate uname, email and password I am passing the values like above.
But when the radio button is ticked for auto generated password it is still handling the validation, I click on next and it is not going to next for because of password field.
And if I check the radio button as let me create the password it goes to next form and when I came back by clicking back it is going to auto generated password again and it is not holding the previous state. For other input fields it is handling the previous values but not in case of radio button scenario.
My full working code sandbox
Answer 1 The reason is you fields: ["uname", "email", "password"] is fixed, password is always to be taken validation.
Solution Need to store state of Form1 in App so you can check if the state of auto generated password is on remove password from the list
App.js
... other code
// need to move state and function form Form to app
const [show_input, setshow_input] = useState(false);
const createInput = () => {
setshow_input(true);
};
const auto_text = () => {
setshow_input(false);
};
const forms = [
{
// validate based on show_input state
fields: show_input ? ["uname", "email", "password"] : ["uname", "email"], //to support multiple fields form
component: (register, errors, defaultValues) => (
<Form1
register={register}
errors={errors}
defaultValues={defaultValues}
auto_text={auto_text}
createInput={createInput}
show_input={show_input}
/>
)
},
{
fields: ["lname"],
component: (register, errors, defaultValues) => (
<Form2
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
},
{
component: (register, errors, defaultValues) => (
<Form3
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
}
];
... other code
Answer 2 When you go next the Form1 is unmounted so its state is destroyed. When you store Form1's state in App.js you will fix this issue too
Bonus: It's prefered to use camalCase (E.g: showInput) rather than underscore (show_input)
The main problem is that you render the forms conditionally so all the previous form values are removed. The solution for this is to keep all forms mounted and just use display: none or display: block depending on which form is selected. This way all values will be persisted whenever you go to next or prev form or submit the form.
The second problem that you didn't remove the password field when it's unmounted so when moveToNext is called the valid argument in triggerValidation callback is always false. I fixed that by setting the fields for Form1 conditionally depending on if the password input is visible or not.
The third problem you are using defaultValues for the wrong purpose. You can get the current form values using getValues() which will return all the current values of the form.
I set the default value for uname field just as an example to show you how defaultValues should be used.
you can check the full solution here: https://codesandbox.io/s/fragrant-forest-75pzs?file=/src/App.js
here are all the changed files:
App.js
import React, { useState } from "react";
import Form1 from "./components/Form1";
import Form2 from "./components/Form2";
import Form3 from "./components/Form3";
import { useForm } from "react-hook-form";
function MainComponent() {
const {
register,
triggerValidation,
defaultValues,
errors,
getValues
} = useForm({
// You can set default values here
defaultValues: {
uname: "Lol"
}
});
console.log("Errors: ", errors);
const [currentForm, setCurrentForm] = useState(0);
// control password input visibility and Form1 fields
const [passwordVisible, setPasswordVisible] = useState(false);
const showPassword = () => {
setPasswordVisible(true);
};
const hidePassword = () => {
setPasswordVisible(false);
};
const forms = [
{
fields: passwordVisible
? ["uname", "email", "password"]
: ["uname", "email"],
component: (register, errors) => (
<Form1
// a key is needed to render a list
key={0}
// this will be used to set the css display property to block or none on each form
shouldDisplay={currentForm === 0}
register={register}
errors={errors}
showPassword={showPassword}
hidePassword={hidePassword}
passwordVisible={passwordVisible}
/>
)
},
{
fields: ["lname"],
component: (register, errors) => (
<Form2
key={1}
shouldDisplay={currentForm === 1}
register={register}
errors={errors}
/>
)
},
{
component: (register, errors) => (
<Form3
key={2}
shouldDisplay={currentForm === 2}
register={register}
errors={errors}
values={getValues()}
/>
)
}
];
const moveToPrevious = () => {
triggerValidation(forms[currentForm].fields).then(valid => {
if (valid) setCurrentForm(currentForm - 1);
});
};
const moveToNext = () => {
triggerValidation(forms[currentForm].fields).then(valid => {
if (valid) setCurrentForm(currentForm + 1);
});
};
const prevButton = currentForm !== 0;
const nextButton = currentForm !== forms.length - 1;
const handleSubmit = e => {
console.log("whole form data - ", getValues());
};
return (
<div>
<div className="progress">
<div>{currentForm}</div>
</div>
{forms.map(form => form.component(register, errors))}
{prevButton && (
<button
className="btn btn-primary"
type="button"
onClick={moveToPrevious}
>
back
</button>
)}
{nextButton && (
<button className="btn btn-primary" type="button" onClick={moveToNext}>
next
</button>
)}
{currentForm === 2 && (
<button
onClick={handleSubmit}
className="btn btn-primary"
type="submit"
>
Submit
</button>
)}
</div>
);
}
export default MainComponent;
Form1
import React from "react";
function Form1({
register,
errors,
shouldDisplay,
passwordVisible,
showPassword,
hidePassword
}) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<form autoComplete="on">
<br />
<div className="form-group">
<label>User name</label>
<input type="text" name="uname" ref={register({ required: true })} />
{errors.uname && <span>required</span>}
<label>Email</label>
<input type="email" name="email" ref={register({ required: true })} />
{errors.email && <span>required</span>}
</div>
<div>
<div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<label className="form_label">Password</label>
<div className="form-check">
<label>
<input
type="radio"
name="auto_pass"
id="Radios1"
value="auto_pass"
className="form-check-input"
defaultChecked={true}
onChange={hidePassword}
/>
Auto generated password
</label>
</div>
<div className="form-check">
<label>
<input
type="radio"
name="auto_pass"
id="Radios2"
value="let_me"
className="form-check-input"
onChange={showPassword}
/>
Let me create the password
</label>
</div>
</div>
{passwordVisible && (
<div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mb-3">
<label className="form_label">Password</label>
<input
type="password"
name="password"
className="form-control"
ref={register({ required: true })}
/>
{errors.password && (
<span className="text-danger">Password is reguired</span>
)}
</div>
)}
</div>
</form>
</div>
);
}
export default Form1;
Form2
import React from "react";
function Form2({ register, errors, shouldDisplay }) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<form autoComplete="on">
<br />
<div className="form-group">
<label>User last name</label>
<input type="text" name="lname" ref={register({ required: true })} />
{errors.lname && <span>required</span>}
</div>
</form>
</div>
);
}
export default Form2;
Form3
import React from "react";
function Form3({ values, shouldDisplay }) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<h3>Want to display all values here like below</h3>
{Object.entries(values).map(([key, value]) => (
<p key={key}>
{key}: {value}
</p>
))}
<br />
<p>So that use can check for any Wrong info</p>
</div>
);
}
export default Form3;

React Form: How to add error message that disappear if the input was typed in

I already built the form in React and it shows the input fields in red borders that'll change to regular borders once someone types it in. I used this example from this React form article link So everything is working except I wanted to add the error message under the input field that displays "Please fill in the blank field" that will disappear once someone starts typing in the field. How do I do this?
Here's my code in Form.js:
import React, { Component } from 'react';
import FormField from './FormFieldBox';
function validate(name, isin) {
// true means invalid, so our conditions got reversed
return {
name: name.length === 0,
isin: isin.length === 0
};
}
export default class PopupForm extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
isin: '',
country: '',
errormessage: ''
}
}
updateInput = (e) =>{
this.setState({[e.target.name]: e.target.value})
}
closePopupSubmit = (e) => {
if (!this.canBeSubmitted()) {
e.preventDefault();
}
let security = { //1.gather security data from form submit
name: this.state.name,
isin: this.state.isin,
country: this.state.country
}
this.props.submitPopup(security); //2.closePopup function, add security data
}
canBeSubmitted() {
const errors = validate(this.state.name, this.state.isin);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return !isDisabled;
}
cancelPopupSubmit = (e) => {
e.preventDefault()
this.props.cancelPopup();
}
render() {
const errors = validate(this.state.name, this.state.isin);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return (
<div className='popup'>
<div className='popup-inner'>
<form onSubmit={this.closePopupSubmit}>
<FormField onChange={this.updateInput} className={errors.name ? "input error" : "input"} label="Name" type="text" name="name" value={this.state.name} />
<FormField onChange={this.updateInput} className={errors.isin ? "input error" : "input"} label="ISIN" type="text" name="isin" value={this.state.isin} />
<FormField onChange={this.updateInput} label="Country" type="text" name="country" value={this.state.country} />
<button type="button" onClick={this.cancelPopupSubmit} className="button">Cancel</button>
<button type="submit" className="button" disabled={isDisabled}>Submit</button>
</form>
</div>
</div>
)
}
}
And my component FormField.js
import React from "react";
const FormBox = props => {
return (
<div className="field">
<label className="label">{props.label}</label>
<div className="control">
<input onChange={props.onChange}
className={props.className}
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder} />
{/* {props.errormessage} */}
</div>
</div>
)
}
export default FormBox;
const FormBox = props => {
return (
<div className="field">
<label className="label">{props.label}</label>
<div className="control">
<input onChange={props.onChange}
className={props.className}
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder} />
</div>
{Boolean(props.value.length) || (
<div className="err-msg">
Please fill in the blank field
</div>
)}
</div>
)
}
There are two ways you can achieve this
First : oninvalid attribute in HTML5 and calling a custom function on that.
Second : along with each element name object in state have a length attribute. In validation function you can check for the length and throw a custom error that you want to display.

How to highlight empty mandatory(*) input field with red border click on button in React?

In below image screenshot I make fields mandatory so click on register button If any fields then that empty field I want to highlight with red border in React how it is possible ?
(https://blueprintjs.com/docs/#core/components/text-inputs)
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: '',
email: '',
password: '',
};
this.handleChange = this.handleChange.bind(this);
this.registerForm = this.registerForm.bind(this);
}
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
registerForm(){
if(this.state.firstName.trim() && this.state.lastName.trim() &&
this.state.email && this.state.password){
console.log("registration successfully..!!");
}else{
console.log("all * marked fields mandatory");
}
}
render() {
return (
<div>
<h2>Fill Registration Details..!!</h2>
<InputGroup placeholder="Enter First Name...*"
name="firstName" value={this.state.firstName} onChange={this.handleChange}/>
<InputGroup placeholder="Enter Last Name...*" name="lastName"
value={this.state.lastName} onChange={this.handleChange}/>
<InputGroup placeholder="Enter your email...*" name="email"
value={this.state.email} onChange={this.handleChange}/>
<InputGroup placeholder="Enter your password...*"name="password"
value={this.state.password} onChange={this.handleChange}/>
<Button intent="Primary" onClick={this.registerForm}>Register</Button>
</div>
)
}
One solution, as #Saraband stated, is to modify your node's class name depending on whether or not your input field contains an error:
<InputGroup
placeholder="Enter your password...*"
name="password"
className={this.state.password.length ? '' : 'error'}
value={this.state.password}
onChange={this.handleChange}
/>
You can then use it with the following CSS that will show a red border (for example) :
.error input
{
border-bottom: 1px solid #eb516d;
}
Another way is to use the native required attribute of the input tag, but this method is hard to customize :
<input type='text' required/>
https://www.w3schools.com/tags/att_input_required.asp
For those who might be looking for a solution to this question, the solution below will only validate once the submit button is clicked. You can add a custom css class to style the input tag.
import React, { useState } from 'react';
const ValidateInput = () => {
// set isSubmitting to false by default
// this will make sure error class is not added by default
const [isSubmitting, setIsSubmitting] = useState(false);
const [inputValue, setInputValue] = useState('');
const submitHandler = (event) => {
event.preventDefault();
// this will trigger the error validation
setIsSubmitting(true);
// add the rest of the logic here
};
return (
<form onSubmit={submitHandler}>
<input
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
className={isSubmitting && !inputValue ? 'error' : undefined}
/>
<button type="submit">Submit</button>
</form>
);
};
export default ValidateInput;
You can create a CSS class - let's say .red-border and add it to your input whenever their value is empty (your component need to be able to use this className prop and pass it down to your <input /> native component)
<InputGroup
placeholder="Enter your password...*"
name="password"
className={!this.state.password.length ? '' : 'red-border'}
value={this.state.password}
onChange={this.handleChange}
/>
Although it can be best to keep this sort of thing inside your InputGroup component thus confining the logic of your component to a single file
class InputGroup extends React.Component {
// code
render () {
return(
// code
<input
value={this.props.value}
className={!this.state.password.length ? '' : 'red-border'}
/>
);
}
};

Warning: Failed prop type: Invalid prop `initialValues` supplied to `Form(AddComment)`

My application has dynamic routes (dynamic route param), in which it contains the redux form.In order to distinguish the form data, I need to post redux form data along with the react route param.
I have passed the react route param as props from the parent component to the child component having the redux form i.e, initial values in props have the param value.I want to initialize the route param to the input field with hidden type.
import React from 'react';
import { Field, reduxForm,propTypes } from 'redux-form';
import submit from '../actions/commentActions'
import connect from 'react-redux';
const validate = values => {
const errors = {}
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.message) {
errors.message = 'Required !!'
}else if (values.message.length > 15) {
errors.message = 'Must be 15 characters or less'
}
return errors
}
const renderField = ({
input,
label,
type,
meta: { touched, error, warning }
}) => (
<div>
<div>
<input {...input} placeholder={label} type={type} className="form-control" />
{touched &&
((error && <span className="text-danger">{error}</span>) )}
</div>
</div>
)
const renderTextAreaField = ({
input,
label,
type,
meta: { touched, error, warning }
}) => (
<div>
<div>
<textarea {...input} rows="3" placeholder={label}
className="form-control shareThought mt-1"></textarea>
{touched &&
((error && <span className="text-danger">{error}</span>) )}
</div>
</div>
)
const AddComment = props => {
const { error,handleSubmit, pristine, reset, submitting,initialValues } = props;
// console.log(initialValues); prints route param i.e honda
// console.log(props);
return (
<div className="leaveComment pb-2">
<form onSubmit={handleSubmit(submit)}>
<Field
name="email"
component={renderField}
type="email"
label="Email Id"
placeholder="Enter Email"
/>
<Field name="message"
component={renderTextAreaField}
label="Share Your thought"
type="text"
/>
<Field name="modelname"
type="text"
component="input"
value={initialValues}
hidden
/>
<span className="text-danger">{error && <span>{error}</span>}</span>
<div className="row mx-0" >
<button type="submit" className="btn btn-sm btn-info btn-block mt-2" disabled={pristine || submitting}>Leave a comment</button>
</div>
</form>
</div>
);
};
export default reduxForm({
form: 'addcommentmsg',
validate
})(AddComment);
I solved this issue by passing initialValues with key-value
let initialValues = {
initialValues: {
modelname: this.props.pageId
}
};
Therefore you dont have to define the initialValues either in input field or props.
I also could make it work only this way:
const mapStateToProps = state => {
return {
initialValues: {
status: state.profile.userStatus
}
}
}
And input has such value:
value={props.initialValues}

Categories

Resources