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.
Related
I have an table with check boxes . On click i have to collect my data in something like this :
"docList" : {
"docID1" : "docNR1",
"docID2" : "docNR2",
}
and after that i have to make a request to the BE . The BE will return me an ZIP file with all clicked or selected documents . Normally is pretty simple , but i have some issues .
I declare my state into the constructor :
constructor(props) {
super(props);
this.state = {
selected: []
}
};
And then i pass my function into the checkbox :
handleCheckboxClick = (e) => {
if(e.target.checked){
this.setState({
selected: [...this.state.selected, e.target.value],
},()=>{
console.log( this.state.selected)
});
} else {
let remove = this.state.selected.indexOf(e.target.value);
this.setState({
selected: this.state.selected.filter((_, i) => i !== remove)
},()=>{
console.log( this.state.selected)
})
}
}
<Checkbox value={rowData.documentId.toString() + rowData.documentNumber.toString()} onClick={this.handleCheckboxClick} />
And from my console i get this :
["1004212942019-DGD-2000000478"]
Basically this is the docID + docNR combined in one string . So how can i seperate them and return an object with two seprated strings for each .
The result is not what i have expected so i will be glad if someone can give me a hand . Thank you
I would use JSON.stringify() and JSON.parse() as follows:
constructor(props) {
super(props);
this.state = {
docList: {}
};
};
<Checkbox value={JSON.stringify({documentId: rowData.documentId, documentNumber: rowData.documentNumber})} onClick={this.handleCheckboxClick} />
handleCheckboxClick = (e) => {
let parsedVal = JSON.parse(e.target.value);
let newDocList = {...this.state.docList};
if(e.target.checked) {
newDocList[parsedVal.documentId] = parsedVal.documentNumber;
} else {
delete newDocList[parsedVal.documentId];
}
this.setState({
docList: newDocList
}, ()=>{
console.log(this.state.docList)
});
}
You can use data attributes - https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
<Checkbox data-docId={rowData.documentId.toString()} data-docNumber={rowData.documentNumber.toString()} onClick={this.handleCheckboxClick} />
Inside handleCheckboxClick you can get the data values in the event object.
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">
I have a form in react that I'm refactoring. I'm going to move most of the state and logic to the parent, because the parent state will be updated with the form result... but I was going to refactor before and can't seem to get a switch statement to work. I was told it would help performance in the long run.
The Validate function is where I'm trying to add a switch statement.
import React from 'react'
import styles from './style.addLibForm.css'
class AddLibForm extends React.Component {
constructor(props) {
super(props);
this.state = {
input: {
title: "",
content: "",
imgURL: ""
},
blurred: {
title: false,
content: false,
imgURL: ""
},
formIsDisplayed: this.props.toggle
};
}
handleInputChange(newPartialInput) {
this.setState(state => ({
...state,
input: {
...state.input,
...newPartialInput
}
}))
}
handleBlur(fieldName) {
this.setState(state => ({
...state,
blurred: {
...state.blurred,
[fieldName]: true
}
}))
}
***//TURN INTO SWITCH STATEMENT!***
validate() {
const errors = {};
const {input} = this.state;
if (!input.title) {
errors.title = 'Title is required';
} //validate email
if (!input.content) {
errors.content = 'Content is required';
}
if (!input.imgURL) {
errors.imgURL = 'Image URL is required';
}
console.log(Object.keys(errors).length === 0);
return {
errors,
isValid: Object.keys(errors).length === 0
};
}
render() {
const {input, blurred} = this.state;
const {errors, isValid} = this.validate();
return (
<div className="flex">
<form
className={styles.column}
onSubmit={(e) =>
{ e.preventDefault();
this.setState({})
return console.log(this.state.input);
}}>
<h2> Add a library! </h2>
<label>
Name:
<input
className={styles.right}
name="title"
type="text"
value={input.title}
onBlur={() => this.handleBlur('title')}
onChange={e => this.handleInputChange({title: e.target.value})}/>
</label>
<br/>
<label>
Content:
<input
className={styles.right}
name="content"
type="text"
value={input.content}
onBlur={() => this.handleBlur('content')}
onChange={e => this.handleInputChange({content: e.target.value})}/>
</label>
<br/>
<label>
Image URL:
<input
className={styles.right}
name="imgURL"
type="text"
value={input.imgURL}
onBlur={() => this.handleBlur('imgURL')}
onChange={e => this.handleInputChange({imgURL: e.target.value})}/>
</label>
<br/>
<p>
<input className={styles.button} type="submit" value="Submit" disabled={!isValid}/>
</p>
{/* CSS THESE TO BE INLINE WITH INPUTS */}
<br/>
{blurred.content && !!errors.content && <span>{errors.content}</span>}
<br/>
{blurred.title && !!errors.title && <span>{errors.title}</span>}
<br/>
{blurred.imgURL && !!errors.imgURL && <span>{errors.imgURL}</span>}
</form>
</div>
);
}
}
export default AddLibForm
I've was putting the switch statement inside the validate function. I tried inputs, errors, this.state.input, this.state.errors, {input}... what am I missing?
Switch statement makes most sense when you need to compare one variable with different values
if(x === 1){
//...
} else if(x === 2) {
//...
} else if(x === 3) {
//...
} else {} {
//...
}```
=>
switch (x) {
case 1:
//...
break;
case 2:
//...
break;
case 3:
//...
break;
default:
//...
}
since it clearly expresses this "1 variable to many values" mapping condition.
In your case though you don't compare one var to many values, you simply check multiple variables for existence, i.e. semantically compare them with a truthy value, so switch will not make much sense.
It's better to leave as is since all these ifs are checking different conditions and are still pretty readable.
Overall you can check this article for a more verbose directions on approaching condition handling more efficiently.
I am attempting to validate an input manually by passing up the chain the input of a text field. Some magic is supposed to happen whereby the following conditions are checked:
if input text matches that already held in an array - error = "already exists" & the text isn't added to the list
if input text is blank - error = "no text input" & the text isn't added to the list
if input text is not blank and does not already exist - run another method to add text to the list
The error is set to null by default
Currently in the input.js file, the {this.props.renderError} line causes an "underfined" output in the console before anything happens. I understand why this occurs, but I wondered if there was any way to stop it?
Functionality-wise: I can get the error message to output, however this appears to run after the text is already placed in the list of tasks...
Checkout the sandbox for this code
App.js (parent)
const tasks = [
{ name: 'task1', isComplete: false },
{ name: 'task2', isComplete: true },
{ name: 'task3', isComplete: false },
]
class App extends React.Component {
constructor() {
super();
this.state = {
error: null,
}
}
render() {
return (
<div>
<Input
createTask={this.createTask.bind(this)}
renderError={this.renderError.bind(this)}
taskList={this.state.tasks}
throwError={this.throwError.bind(this)}
/>
</div>
)
}
createTask(task, errorMsg) {
this.throwError(errorMsg);
if (this.state.error) {
return;
} else {
this.setState((prevState) => {
prevState.tasks.push({ name: task, isComplete: false });
return {
tasks: prevState.tasks
}
})
}
}
throwError(errorMsg) {
if (errorMsg) {
this.setState((prevState) => {
prevState.error = errorMsg;
return {
error: prevState.error
}
})
}
return;
}
renderError() {
if (this.state.error) {
return <div style={{ color: 'red' }}>{this.state.error}</div>
}
}
Input.js (child)
render() {
return (
<form ref="inputForm" onSubmit={this.handleCreate.bind(this)}>
<TextField placeholder="Input.js"/>
<Button type="submit">Click me</Button>
{this.props.renderError()}
</form>
)
}
validateInput(taskName) {
if (!taskName) {
return '*No task entered';
} else if (this.props.taskList.find(todo => todo.name.toLowerCase() === taskName.toLowerCase())) {
return '*Task already exists'
} else {
return null;
}
}
handleCreate(event) {
event.preventDefault();
// Determine task entered
var newTask = this.refs.inputForm[0].value;
// Constant for error message returned
const validInput = this.validateInput(newTask);
// If error message produced - trigger error to be shown & end
if (newTask) {
this.props.createTask(newTask, validInput);
this.refs.inputForm.reset();
}
}
Update
I have since found that I can make this work if I move the renderError and throwError methods to input.js and also transfer across the state property error.
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)
}
}