I'm trying to have form input elements, which are uncontrolled because of our use of jQuery UI DatePicker and jQuery maskMoney, render errors underneath them as soon as user types something invalid for that field, as well as disable the button on any of the errors. For some reason, none of that is working right.
Main component
is something like the following:
class MainComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
payrates: [
new PayRate(new Date(2019, 2, 1), 0.00),
],
errors : {
rate: '',
date: ''
},
currentPayRate : new PayRate() // has Rate and EffectiveDate fields
}
// binding done here
this.appendValue = this.appendValue.bind(this)
this.updateCurrentPayRate = this.updateCurrentPayRate.bind(this)
this.updateCurrentPayRateDate = this.updateCurrentPayRateDate.bind(this)
this.updateCurrentPayRateAmount = this.updateCurrentPayRateAmount.bind(this)
this.validate = this.validate.bind(this)
}
/**
* #param { PayRate } newPayRate
**/
updateCurrentPayRate(newPayRate) {
this.setState({
...this.state,
currentPayRate : newPayRate
})
}
updateCurrentPayRateDate(dateString) {
const newPayRate = Object.assign(new PayRate(), this.state.currentPayRate, { EffectiveDate : new Date(dateString) } )
this.validate(newPayRate)
this.updateCurrentPayRate(newPayRate)
}
updateCurrentPayRateAmount(amount) {
const newPayRate = Object.assign(new PayRate(), this.state.currentPayRate, { Rate : Number(amount) } )
this.validate(newPayRate)
this.updateCurrentPayRate(newPayRate)
}
/**
* #param { PayRate } value
**/
appendValue(value) {
console.log("trying to append value: ", value)
if (this.validate(value)) {
this.setState({...this.state,
payrates : this.state.payrates.concat(this.state.currentPayRate)})
}
}
/**
* #param { PayRate } value
**/
validate(value) {
// extract rate,date from value
const rate = value.Rate,
date = value.EffectiveDate
console.log("value == ", value)
let errors = {}
// rate better resolve to something
if (!rate) {
errors.rate = "Enter a valid pay rate amount"
}
// date better be valid
if ((!date) || (!date.toLocaleDateString)) {
errors.date = "Enter a date"
}
else if (date.toLocaleDateString("en-US") === "Invalid Date") {
errors.date = "Enter a valid pay rate date"
}
console.log(errors)
// update the state with the errors
this.setState({
...this.state,
errors : errors
})
const errorsToArray = Object.values(errors).filter((error) => error)
return !errorsToArray.length;
}
render() {
return <div>
<DateList dates={this.state.payrates}/>
<NewPayRateRow
value={this.state.currentPayRate}
errors={this.state.errors}
onChange={this.updateCurrentPayRate}
onPayRateAmountChange={this.updateCurrentPayRateAmount}
onPayRateDateChange={this.updateCurrentPayRateDate}
onAdd={this.appendValue}
/>
</div>
}
}
The "form" component
Has the following implementation:
class NewPayRateRow extends React.Component {
constructor(props) {
super(props)
}
render() {
console.log(Object.values(this.props.errors).filter((error) => error))
return <span class="form-inline">
<RateField
errors={this.props.errors.rate}
onKeyUp={(e) => {
// extract the value
const value = e.target.value
this.props.onPayRateAmountChange(value)
}}
/>
<DateInput
errors={this.props.errors.date}
onChange={this.props.onPayRateDateChange}
/>
<button onClick={(e) => {
this.props.onAdd(this.props.value)
}}
disabled={Object.values(this.props.errors).filter((error) => error).length}>Add New Pay Rate</button>
</span>
}
}
An uncontrolled input component
where the issue definitely happens:
class DateInput extends React.Component {
constructor(props) {
super(props);
// do bindings
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
$('#datepicker').datepicker({
changeMonth: true,
changeYear: true,
showButtonPanel: true,
yearRange: "-116:+34",
dateFormat: 'mm/dd/yy',
// telling jQuery UI to pass its event to React
onSelect : this.handleChange
});
}
componentWillUnmount() {
$('#datepicker').datepicker('destroy')
}
// handles a change to the input field
handleChange(value) {
this.props.onChange(value)
}
render() {
const fieldIsInvalid = this.props.errors || ''
return <div class="col-md-2">
<input
id="datepicker"
className={"datepicker form-control " + fieldIsInvalid }
placeholder="mm/dd/yyyy"
onChange={(e) => this.props.onChange(e.target.value) }>
</input>
<div>
{this.props.errors}
</div>
</div>
}
}
For some reason, even though I'm selecting via the datepicker widget the value, the errors don't change:
However, when I go to comment out all the validate calls, it adds the fields no problem.
I did some caveman debugging on the value I was passing to validate to ensure that I was passing it truthy data.
Why is this.state.error not updating correctly, via the components?!
UPDATE: I went to update just the pay rate, initially, and the errors rendered correctly, and from going through the code, I found that this.setState was actually setting the state. However, when I went to trigger change on the input money field, this.setState was getting hit, and errors object, was empty (which is correct), but somehow, this.setState wasn't actually updating the state.
I fixed the issue!
What I did
Instead of persisting errors in the global state, and instead of passing validate, to set the global state, to the methods, I maintain it as function defined outside the main component's class, like this :
/**
* Validates a PayRate
* #param { PayRate } value
* #returns { Object } any errors
**/
function validate(value = {}) {
// extract rate,date from value
const rate = value.Rate,
date = value.EffectiveDate
let errors = {}
// rate better resolve to something
if (!rate) {
errors.rate = "Enter a valid pay rate amount"
}
// date better be valid
if ((!date) || (!date.toLocaleDateString)) {
errors.date = "Enter a date"
}
else if (date.toLocaleDateString("en-US") === "Invalid Date") {
errors.date = "Enter a valid pay rate date"
}
return errors
}
Note the much simpler implementation. I then no longer need to call validate on the updateCurrentPayRate... methods.
Instead, I invoke it on NewPayRateRow.render (which I can now do because it's not touching state at all, avoiding any invariant violation), save the result to a local const variable, called errors, and use that instead of this.props.errors. Though, truth be told, I could probably put validate back in this.props to achieve a layer of abstraction/extensibility.
Also, I took Pagoaga's advice and used className instead of class (I don't have that as muscle memory yet).
You have a "class" attribute inside several of your render functions, replacing it with "className" will allow the error to show up : https://codepen.io/BPagoaga/pen/QoMXmw
return <div className="col-md-2">
Related
I am new to ReactJS. I have created a service that I have called inside componentWillMount() since I want the data as soon as the page loads.
Now I want to use the same service when there is a dropdown change and update with the new value.
I have called the service inside onChangeDropDown() but it appears that updated value is not rendered correctly.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import config from '../../../../config';
import {
Container, Divider, Accordion, Icon, Transition, Label, Table
} from 'semantic-ui-react';
import Utilities, { Button, PtInput, PtInputShort, PtInputIcon, PtSelect, PtDateInput, PtInputConditionTab, PtInputNextToEachOther } from '../../../../grit-utilities';
import ScrollArea from 'react-scrollbar';
export default class Future extends Component {
constructor(props) {
super(props);
this.state = {
...this.props.futureModel.pavementcondition,
selectedYear: new Date().getFullYear(),
PQI: 'N.A.',
Age: 'N.A.',
Aadt: 'N.A.',
Esals: 'N.A.',
CumEsals: 'N.A.',
pqiArray: []
};
}
getYearOptions = () => {
const yearOptions = [];
for (let i = 0; i < 35; i++) {
yearOptions.push({ key: new Date().getFullYear() + i, value: new Date().getFullYear() + i, label: new Date().getFullYear() + i});
}
return yearOptions;
}
fetchProjectionData = (selectedYear) => {
axios.get(`${config.server}\\fetchFutureData\\${this.props.futureModel.route.id}\\${selectedYear}`).
then((res) => {
if (res.data.result[0]) {
let data = res.data.result[0];
this.setState({
//PQI: data.PSR,
Age: data.Age,
Esals: data.Esals,
Aadt: data.AADT,
CumEsals: data.CumEsal
});
}
});
}
render() {
const yearOptions = this.getYearOptions();
return (
<Container>
<ScrollArea
speed={1}
className={!window.mobileAndTabletcheck() ? "area_desktop" : "area_mobile"}
contentClassName="content"
smoothScrolling={true}>
<PtSelect name="selectedYear" defaultVal={this.state.selectedYear} label="Select Year" options={yearOptions} onChange={this.onChangeDropdown.bind(this)} />
<PtInput disabled placeholder="Not Available" name="PQI" value={this.state.PQI ? this.state.PQI:''} label="PQI - Pavement Quality Index" disabled name="PQI" />
<PtInput disabled placeholder="Not Available" name="Age" value={this.state.Age ? this.state.Age:''} label="Age" disabled name="Age" />
<PtInput disabled placeholder="Not Available" name="Aadt" value={this.state.Aadt ? Math.round(this.state.Aadt) : ''} label="AADT" disabled name="AADT" />
<PtInput disabled placeholder="Not Available" name="Esals" value={this.state.Esals ? Math.round(this.state.Esals):''} label="ESALs" disabled name="ESALs" />
<PtInput disabled placeholder="Not Available" name="CumEsals" value={this.state.CumEsals ? Math.round(this.state.CumEsals) : ''} label="Cumulative ESALs" disabled name="CumESALs" />
</ScrollArea>
</Container>
);
}
/* This function actually gets the PQI value for the current year based on routeId and year from FutureTabView hard table
Date: 09/25/2019
*/
getfirstPQI = () =>{
fetch(`${config.server}/getfirstpqi/`+this.props.futureModel.route.id+`/`+this.state.selectedYear)
.then(response=>response.json())
.then(data=>{
//console.log(data[0]['PSR'])
this.setState({PQI:data[0]['PSR']})
})
}
onChangeDropdown = (e) => {
const { target } = e;
const { name, value } = target;
this.setState({ [name]: parseInt(value) });
this.fetchProjectionData(value);
this.getfirstPQI();
}
componentDidMount() {
this.fetchProjectionData(this.state.selectedYear);
this.getfirstPQI();
}
}
I have done this code and what I am trying to do it as soon as the page loads it should show the value that is being received from the service. Example: the current year is 2019 and as soon as the page loads the value for the year 2019 is being rendered. Now I have a dropdown of years.So when I choose 2020 the value is not getting updated whereas when I choose 2021 it updates the value with the value of 2020. So the updating value is delaying always. How to render the value as soon as there is a change in dropdown?
React's setState() is asynchronous, meaning if you want to access the updated state after setting it then you should use the callback which runs when the state has successfully updated.
this.setState({ [name]: parseInt(value) }, () => {
this.fetchProjectionData(value);
this.getfirstPQI();
});
If you don't use the callback then getfirstPQI() may be reading the old state.
I've created this method that gets the state of the calculator input and checks if its empty or not. I need help with two things:
What's the cleanest way to add here a validation to check if each input is also a number and outputs and error "Input must be a number"
Currently I have one error message that fires whether all the inputs are present or not, where what I want is for it to validate each input separately and fire an error under each one. How do I do it but still keep this function concise?
constructor(props) {
super(props);
this.state = {
price: 0,
downP: 0,
term: 0,
interest: 0,
error: ''
};
}
handleValidation = () => {
const {
price,
downP,
loan,
interest,
} = this.state;
let error = '';
let formIsValid = true;
if(!price ||
!downP ||
!loan ||
!interest){
formIsValid = false;
error = "Input fields cannot be empty";
}
this.setState({error: error});
return formIsValid;
}
And then this is the error message
<span style={{color: "red"}}>{this.state.error}</span>
If you want to keep your error messages separate I would recommend to reorganize your state.
So scalable solution (you may add more controls by just adding them to state) may look like:
class NumberControlsWithErrorMessage extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: [
{ name: 'price', value: 0, error: ''},
{ name: 'downP', value: 0, error: '' },
{ name: 'term', value: 0, error: '' },
{ name: 'interest', value: 0, error: '' }
]
};
}
handleInputChange = (idx, event) => {
const target = event.target;
const name = target.name;
let error = '';
if (isNaN(target.value)) {
error = `${name} field can only be number`
}
if (!target.value) {
error = `${name} field cannot be empty`
}
this.state.inputs[idx] = {
...this.state.inputs[idx],
value: target.value,
error
}
this.setState({
inputs: [...this.state.inputs]
});
}
render() {
return (
<form>
{this.state.inputs.map((input, idx) => (
<div>
<label htmlFor="">{input.name}</label>
<input type="text" value={input.value} onChange={(e) => this.handleInputChange(idx, e)}/>
{input.error && <span>{input.error}</span> }
</div>
))}
</form>
);
}
}
Working example
Also if you are building a complex form, you may want to try some React solution for forms, where all the mechanism for listening to events, state updates, validatoin are already handled for you. Like reactive-mobx-form
A straightforward way of handling multiple objects needing validation is to store an errors object in your state that has a property for each input field. Then you conditionally render the error underneath each input field depending on whether or not it has an error. Here is a very basic example:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {
price: 0, downP: 0, term: 0, interest: 0,
errors: { price: '', downP: '', term: '', interest: '' }
};
}
handleValidation = () => {
const { price, downP, loan, interest } = this.state;
let errors = { price: '', downP: '', term: '', interest: '' };
if (!price) {
errors.price = 'Price is required';
} else if (isNaN(price)) {
errors.price = 'Price must be a number';
}
if (!downP) {
errors.downP = 'Down Payment is required';
}
// Rest of validation conditions go here...
this.setState({ errors });
}
render() {
const { errors } = this.state;
return (
<form>
<input name="price" value={this.state.price} onChange={this.handleChange} />
{errors.price != '' && <span style={{color: "red"}}>{this.state.errors.price}</span>}
<input name="downP" value={this.state.downP} onChange={this.handleChange} />
{errors.downP != '' && <span style={{color: "red"}}>{this.state.errors.downP}</span>}
{/** Rest of components go here */}
</form>
);
}
}
You can choose whether or not to run validation once the form submits or on every keypress and that will affect how when those messages appear and disappear, but this should give you an idea on how you would manage error messages specific to each input field.
You can do this:
handleValidation() {
const { price, downP,loan, interest} = this.state;
// only each block with generate error
if (!price || isNaN(price)) {
this.setState({ error: 'price is not valid' });
} else if (!downP || isNaN(downP)) {
this.setState({ error: 'downP is not valid' });
} else {
this.setState({error: ""})
// submit code here
}
}
Note: you dont need to return anything. It will update the error state and only submit the form if it goes into no error (else{}) part
and for render() part add this:
{(this.state.error !== '')
? <span style={{color: "red"}}>{this.state.error}</span>
: ''
}
If you want validation msg on each add errPrice, errDownP and so on to the state, and check for them in render like (this.state.errPrice!== '') {} and so on.
One solution assuming you want a one size fits all error message only checking if it was a number or not would be to put them into an array and set error if the input is not a number.
const inputs = [ price, downP, loan, interest ]
inputs.map(input => {
if (!input || isNaN(input)){
error = "Input must be a number"
formIsValid = false
}
}
this.setState({error})
Something like that maybe.
Ultimately I am trying to create a piano like application that you can control by click or key down. I want each keyboard key to control a certain note. Each "piano" key is a component which has a keycode property and a note property. When the event.keycode matches the keycode property I want the associated note to play. What is the best strategy to go about this?
I have tried using refs and playing around with focus on componentDidMount. I cant seem to wrap my head around how this should work.
class Pad extends React.Component {
constructor(props) {
super(props)
this.state = {
clicked: false
}
this.clickedMouse = this.clickedMouse.bind(this)
this.unClickedMouse = this.unClickedMouse.bind(this)
this.handleDownKeyPress = this.handleDownKeyPress.bind(this)
this.handleUpKeyPress = this.handleUpKeyPress.bind(this)
}
clickedMouse(e) {
this.setState({ clicked: true })
this.props.onDown(this.props.note)
console.log(e)
}
unClickedMouse(e) {
this.setState({ clicked: false })
this.props.onUp(this.props.note)
}
handleDownKeyPress(e) {
if (e.keyCode === this.props.keyCode && this.state.clicked === false) {
this.setState({ clicked: true })
this.props.onDown(this.props.note)
}
}
handleUpKeyPress(e) {
this.setState({ clicked: false })
this.props.onUp(this.props.note)
}
render() {
return (
<div
className='pad'
onMouseUp={this.unClickedMouse}
onMouseDown={this.clickedMouse}
onKeyDown={this.handleDownKeyPress}
onKeyUp={this.handleUpKeyPress}
tabIndex='0'
/>
);
}
}
export default Pad
class Pads extends React.Component {
constructor(props) {
super(props);
// tone.js build
this.synth = new Tone.Synth().toMaster()
this.vol = new Tone.Volume(0)
this.synth.chain(this.vol, Tone.Master)
// bindings
this.onDownKey = this.onDownKey.bind(this);
this.onUpKey = this.onUpKey.bind(this);
}
onDownKey(note) {
console.log(`${note} played`);
this.synth.triggerAttack(note);
}
onUpKey(note) {
this.synth.triggerRelease();
}
render() {
const { octave } = this.props
return (
<div className="pad-grid">
<Pad
keyCode={65}
note={`C${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={70}
note={`Db${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={83}
note={`D${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
<Pad
keyCode={68}
note={`Eb${octave}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
</div>
);
}
}
export default Pads
Heres a codepen so you can check it out in action https://codepen.io/P-FVNK/pen/XopBgW
Final Code: https://codesandbox.io/s/5v89kw6w0n
So I made quite a number of changes to enable the functionality you wanted.
Is there a way to maybe loop through the repeated components and their properties to match event.keyCode to correct keycode property?
To enable this, I decided to create an object array containing the possible keys and their notes. I changed them from the now deprecated KeyboardEvent.keyCode to KeyboardEvent.key instead. You could use KeyboardEvent.code as well.
this.padCodes = [
{
key: "a",
note: "C"
},
{
key: "f",
note: "Db"
},
{
key: "s",
note: "D"
},
{
key: "d",
note: "Eb"
}
];
I moved the keydown event listeners from the individual <Pad />s to the parent component and attached the listener to the document instead.
document.addEventListener("keydown", e => {
let index = this.state.pads.findIndex(item => item.key === e.key);
let { octave } = this.context;
if (this.state.pressed === false) {
this.onDownKey(`${this.state.pads[index].props.note}${octave}`);
this.setState({
activeNote: this.state.pads[index].note,
pressed: true
});
}
});
document.addEventListener("keyup", e => {
this.onUpKey(this.state.activeNote);
this.setState({
activeNote: "",
pressed: false
});
});
You may also notice I decided to use the Context instead of props. That's just personal preference, but I rather have one main reference to the octave state rather than passing the prop to each child component.
After that it was just a matter of ensuring that the functions made the calls to the same function and passed both the note and octave.
To reduce some of the clutter I made an array of <Pad />s which I later rendered instead of typing them out one by one.
let newPadCodes = this.padCodes.map(x => (
<Pad
key={x.key.toString()}
note={`${x.note}`}
onDown={this.onDownKey}
onUp={this.onUpKey}
/>
));
this.setState({
pads: newPads
});
//And later
//Render if this.state.pads.length is > 0
{this.state.pads.length > 0 &&
this.state.pads}
That just about covers all I did. Here's the codesandbox with the modified code: https://codesandbox.io/s/5v89kw6w0n
I am currently building a react app where I need a form with the pre-populated values in it when a user wants to update his/her profile. In the add new user form, It's fine to have the drop-down in its default state. But when a user wants to update profile then there is a project and a group dropdown that needs to have a default value i.e. the value when the user was created. Means the drop-down should be populated with the project and the group associated with it.
<Select
multi={true}
simpleValue
required
value={value}
options={[{value:'One', label:'PROJECTONE'},{value:'Two', label:'PROJECTTWO'}]}
onChange={handleInputChange}
/>
So I need a dropdown with the prepopulated projects i.e. PROJECTONE and PROJECTTWO both.
This is the ScreenShot of my update profile. I have the value which I want to set in the dropdown but if I set comma separated string then when I want to remove that option it is not being affected i.e. I am not able to remove that option.
Question Update 1:
So here is my full component
export interface IInputProps {
required?: boolean;
type: string;
placeholder?: string;
menuItems?: Object[];
isDisabled?: boolean;
onSelect?: (value) => void;
defaultValue?: string;
id?: string;
multi?: boolean;
searchable?: boolean;
}
export interface IInputState {
value: string;
}
export class Input extends React.PureComponent<IInputProps, IInputState> {
constructor({ defaultValue }) {
super();
this.state = { value: (defaultValue || '')};
}
componentWillReceiveProps(nextProps) {
if (!nextProps.defaultValue) {
return;
}
const { state: { value } } = this;
if (nextProps.defaultValue !== value) {
this.setState({
value: nextProps.defaultValue
});
}
}
handleInputChange = (selectedValue) => {
this.setState({
value: selectedValue,
});
}
get value() {
return this.state.value;
}
render() {
const { props: { searchable, multi, id, menuItems, required, isDisabled, placeholder, type, defaultValue },
state: { value, dropDownValue }, handleInputChange } = this;
return (
<Select
multi={multi ? multi : false}
simpleValue
searchable={searchable ? searchable : false}
disabled={isDisabled ? isDisabled : false}
required
value={value}
placeholder={placeholder ? placeholder : 'Select...'}
options={menuItems}
onChange={handleInputChange}
/>
);
}
}
}
I am using this input component whereever I need a Select Input and passing the props.
When I use this component as..
<Input
multi={true}
defaultValue="PROJECTONE,PROJECTTWO"
ref="myProjects"
id="myProjects"
menuItems={[{value:'One', label:'PROJECTONE'},{value:'Two', label:'PROJECTTWO'}]}
/>
Then nothing is being set in it's value.
I am using component will recieve props to check if there is any default value passed if it is passed then I am setting the value.
And Once the value is being set on the dropdown I can not remove it using it's cross button that's the main problem here.
There is a problem in your componentWillReceiveProps function:
```
componentWillReceiveProps(nextProps) {
if (!nextProps.defaultValue) {
return;
}
const { state: { value } } = this;
if (nextProps.defaultValue !== value) {
this.setState({
value: nextProps.defaultValue
});
}
}
```
This function can be caused by changes to props or state.
You check nextProps.defaultValue !== value in here, once you check an option of the select, it's value may not be equal to the nextProps.defaultValue, then you always set the value state to the defaultValue, so you can not get the right value of the option.
You need also check whether the nextProps.defaultValue and the this.props.defaultValue is same or not, so that you will get the right changed value:
```
componentWillReceiveProps(nextProps) {
if (!nextProps.defaultValue) {
return;
}
const { state: { value } } = this;
if (nextProps.defaultValue !== value && nextProps.defaultValue !== this.props.defaultValue) {
this.setState({
value: nextProps.defaultValue
});
}
}
```
I have recently started working on react.js, while creating the login page I have used setstate method to set the value of userEmail to text box.
I have created a method which checks the validity of email address and I am calling it every time when user enters a new letter.
handleChangeInEmail(event) {
var value = event.target.value;
console.log("change in email value" + value);
if(validateEmailAddress(value) == true) {
this.setState(function() {
return {
showInvalidEmailError : false,
userEmailForLogin: value,
}
});
} else {
this.setState(function() {
return {
showInvalidEmailError : true,
userEmailForLogin: value
}
});
}
This method and userEmailForLogin state is passed in render method as
<EmailLoginPage
userEmailForLogin = {this.state.userEmailForLogin}
onHandleChangeInEmail= {this.handleChangeInEmail}
/>
I am using the method to validate the email address and the method is
validateEmailAddress : function(emailForLogin) {
if (/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(emailForLogin)) {
return true;
}
return false;
},
I am using this method and state in render of EmailLoginPage as <input type="text" name="" placeholder="Enter Email" className="email-input-txt" onChange={props.onHandleChangeInEmail} value = {props.userEmailForLogin}/>
This is working fine in normal case , but when I try to input a large email addess say yjgykgkykhhkuhkjhgkghjkhgkjhghjkghjghghkghbghbg#gmail.com, it crashes
IMO the frequent change in state is causing this but I couldn't understand what should be done to get rid of this.
I think issue is with the regex only, i tried with other and it's working properly.
Instead of writing the if/else inside change function simply you are write it like this:
change(event) {
var value = event.target.value;
this.setState({
showInvalidEmailError : this.validateEmailAddress(value),
value: value,
});
}
Copied the regex from this answer: How to validate email address in JavaScript?
Check the working solution:
class App extends React.Component {
constructor(){
super();
this.state = {
value: '',
showInvalidEmailError: false
}
this.change = this.change.bind(this);
}
change(event) {
var value = event.target.value;
this.setState(function() {
return {
showInvalidEmailError : this.validateEmailAddress(value),
value: value,
}
});
}
validateEmailAddress(emailForLogin) {
var regex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(regex.test(emailForLogin)){
return true;
}
return false;
}
render() {
return(
<div>
<input value={this.state.value} onChange={this.change}/>
<br/>
valid email: {this.state.showInvalidEmailError + ''}
</div>
);
}
}
ReactDOM.render(
<App/>,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
You could use Lodash's debounce function so that the check function is not called unless the user stops typing for x amount of time (300ms in my scenario below).
_this.debounceCheck = debounce((value) => {
if(validateEmailAddress(value)) {
this.setState(function() {
return {
showInvalidEmailError : false,
userEmailForLogin: value,
}
});
} else {
this.setState(function() {
return {
showInvalidEmailError : true,
userEmailForLogin: value
}
});
}
}, 300)
handleChangeInEmail(event) {
_this.debounce(event.target.value)
}
A solution using debounce. This way multiple setState can be reduced.
DEMO: https://jsfiddle.net/vedp/kp04015o/6/
class Email extends React.Component {
constructor (props) {
super(props)
this.state = { email: "" }
}
handleChange = debounce((e) => {
this.setState({ email: e.target.value })
}, 1000)
render() {
return (
<div className="widget">
<p>{this.state.email}</p>
<input onChange={this.handleChange} />
</div>
)
}
}
React.render(<Email/>, document.getElementById('container'));
function debounce(callback, wait, context = this) {
let timeout = null
let callbackArgs = null
const later = () => callback.apply(context, callbackArgs)
return function() {
callbackArgs = arguments
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}