I have a function in React I am using to check the validation state of a form on submit.
The form contains 2 types of inputs.
Text and Number
However so I may have some control over the length of numbers in the field, the number input prop is set as text with a maxLength prop applied.
What I need to now do is validate that when submitting the form, the values in those inputs are indeed numbers.
My state is:
state = {
firstName: '',
lastName: '',
accountNumber: '',
sortCode1: '',
sortCode2: '',
sortCode3: ''
}
I am attempting to check this using the following....
checkValid = state => {
const rgx = new RegExp(/^[0-9]{0,9}$/)
const result = Object.keys(state).every(key => {
if (key.match(/(firstName|lastName|)/)) {
return !!state[key]
}
return rgx.test(state[key])
})
return result
}
What I am trying to achieve is a check if on firstName and lastName to ensure there are values and then a check on all other props to ensure they are numbers and numbers only.
I cannot seem to make this work though as the form either always returns true or always returns false depending on how I amend the code.
As soon as any field is invalid, I would like to simply return false.
Got some small errors with the regex. Below code should work. =) This one will return false as soon as one field is empty or the value isn't a number on the fields where you want a number.
checkValid = state => {
const rgx = new RegExp(/^[0-9]*$/)
const result = Object.keys(state).every(key => {
// If field is empty return false
if (state[key] === '') return false;
// If on firstName or lastName return true as we already know that the field isn’t empty
if (key.match(/^(firstName|lastName)$/)) return true;
// If not firstName or lastName test the field with rgx
return rgx.test(state[key])
})
return result;
}
It looks like you are trying to validate firstName and lastName based on their values simply being truthy and then the subsequent fields based on them being a number?
checkValid = ({ firstName, lastName, ...rest }) => {
const result = !!firstName && !!lastName && Object.keys(rest).every(key => !isNaN(rest[key]))
return result
}
By deconstructing state you can pick off properties and perform validation separately, with less complex code.
If your only concern is that the other fields are in fact a number, isNan should work.
Related
I have question about pnp/sp PeoplePicker.
PeoplePicker has property "required", but wen i use it in my form it is ignored.
This is my PeoplePicker code:
<PeoplePicker
required={true}
context={this.props.spContext}
personSelectionLimit={1}
onChange={this.hcRequestorPP}
showHiddenInUI={false}
principalTypes={[PrincipalType.User]}
ensureUser={true}
resolveDelay={1000}
defaultSelectedUsers={this.props.pRequestor}
disabled={false} />
What am I doing wrong?
Helo,
you are not doing anything wrong :-) Required property based on my knowledge puts asterisk next to people picker label - that's all.
If you want check in the form if user has put something into people picker field, you have to check it by yourself.
My version of form is:
every form value is stored in state.
every form field with its definition (name, type, required, ...) is stored in props (or state, or const, etc.).
before submitting form, I am checking if every required value is filled (code below)
if it is not filled I put error message under the field.
good example to start is here: https://github.com/pnp/sp-dev-fx-webparts/tree/master/samples/react-list-form
checking required
let requiredError: boolean = false;
let fieldErrors: { [fieldName: string]: string } = {...this.state.fieldErrors};
// check required
for (let i: number = 0; i < this.state.fieldsSchema.length; i++) {
if ((this.state.fieldsSchema[i].Required) && (!this.state.data[this.state.fieldsSchema[i].InternalName]) && (this.state.fieldsSchema[i].InternalName !== 'Aktivita')) {
requiredError = true;
fieldErrors = {
...fieldErrors,
[this.state.fieldsSchema[i].InternalName]: strings.FormFields.RequiredValueMessage
};
}
}
if (requiredError === true) {
this.setState({
...this.state,
fieldErrors: fieldErrors,
requiredFieldEmpty: requiredError
});
return;
}
I'm using react-select and react-final-form for conditional dropdowns, where options for the second select are provided by a <PickOptions/> component based on the value of the first select (thanks to this SO answer).
Here is the component:
/** Changes options and clears field B when field A changes */
const PickOptions = ({ a, b, optionsMap, children }) => {
const aField = useField(a, { subscription: { value: 1 } });
const bField = useField(b, { subscription: {} });
const aValue = aField.input.value.value;
const changeB = bField.input.onChange;
const [options, setOptions] = React.useState(optionsMap[aValue]);
React.useEffect(() => {
changeB(undefined); // clear B
setOptions(optionsMap[aValue]);
}, [aValue, changeB, optionsMap]);
return children(options || []);
};
It clears the second select when the value of the first one changes by changeB(undefined). I've also set the second select to the first option in an array by passing initialValue. As I need to initialize the values from the state, I ended up with the following code:
initialValue={
this.state.data.options[index] &&
this.state.data.options[index].secondOption
? this.state.data.options[index]
.secondOption
: options.filter(
option => option.type === "option"
)[0]
}
But it doesn't work. Initial values from the state are not being passed to the fields rendered by <PickOptions/>. If I delete changeB(undefined) from the component, the values are passed but then the input value of the second select is not updated, when the value of the first select changes (even though the options have been updated). Here is the link to my codesandbox.
How can I fix it?
I was able to get this to work by taking everything that is mapped by the fields.map() section and wrapping it in it's own component to ensure that each of them have separate states. Then I just put the changeB(undefined) function in the return call of the useEffect hook to clear the secondary selects after the user selects a different option for the first select like so:
React.useEffect(() => {
setOptions(optionsMap[aValue]);
return function cleanup() {
changeB(undefined) // clear B
};
}, [aValue, changeB, optionsMap]);
You can see how it works in this sandbox: React Final Form - Clear Secondary Selects.
To change the secondary select fields, you will need to pass an extra prop to PickOptions for the type of option the array corresponds to. I also subscribe and keep track of the previous bValue to check if it exists in the current bValueSet array. If it exists, we leave it alone, otherwise we update it with the first value in its corresponding optionType array.
// subscibe to keep track of previous bValue
const bFieldSubscription = useField(b, { subscription: { value: 1 } })
const bValue = bFieldSubscription.input.value.value
React.useEffect(() => {
setOptions(optionsMap[aValue]);
if (optionsMap[aValue]) {
// set of bValues defined in array
const bValueSet = optionsMap[aValue].filter(x => x.type === optionType);
// if the previous bValue does not exist in the current bValueSet then changeB
if (!bValueSet.some(x => x.value === bValue)) {
changeB(bValueSet[0]); // change B
}
}
}, [aValue, changeB, optionsMap]);
Here is the sandbox for that method: React Final Form - Update Secondary Selects.
I also changed your class component into a functional because it was easier for me to see and test what was going on but it this method should also work with your class component.
Based on the previous answer I ended up with the following code in my component:
// subscibe to keep track of aField has been changed
const aFieldSubscription = useField(a, { subscription: { dirty: 1 } });
React.useEffect(() => {
setOptions(optionsMap[aValue]);
if (optionsMap[aValue]) {
// set of bValues defined in array
const bValueSet = optionsMap[aValue].filter(x => x.type === optionType);
if (aFieldSubscription.meta.dirty) {
changeB(bValueSet[0]); // change B
}
}
}, [aValue, changeB, optionsMap]);
This way it checks whether the aField has been changed by the user, and if it's true it sets the value of the bField to the first option in an array.
I am using one of react phone input library and getting the value of phone number is in "+9901231231234" format . I want only last ten value is in integer form. So i am converting them in number.
if (this.validator.allValid()) {
const phnNo = this.state.phone // +9901231231234
if(phnNo.length>10){
var lastTen = phnNo.substr(phnNo.length - 10);
const subphone = Number(lastTen) //1231231234
this.setState({ phone: subphone }) // Now I want to set the this.state.phone = 1231231234
alert(this.state.phone) // +9901231231234
}
} else {
this.validator.showMessages();
// rerender to show messages for the first time
// you can use the autoForceUpdate option to do this automatically`
this.forceUpdate();
}
But I can not change the current state value with the new value i am generating.Thank you.
The setState function will not update your values synchronously, your state will not be changed right away. The second parameter of it allows you to give a callback function triggering when the state has been mutated :
this.setState({ phone: subphone }, () => { alert(this.state.phone) //1231231234 })
My form only allows user to type their email without #domainname. for e.g. tomdickharyy#email.com the bit from #email.com is predefined. My handle change function will concatenate "#email.com" at the end. Once the form is submitted on my state i set {email : ''} and the form is empty as expected.
The problem is when I type a new email the form will concatenate #email.com for every letter typed. I don't know how to fix this problem, any help would be great.
handleChange({ target: {name, value} }){
const domainEmail = '#email.com';
name === "email" ? this.setState({ [name]: value + domainEmail}) : this.setState({ [name]: value});
} ```
You have two options:
1) Remove domainEmail and append it again on each change. Some very rude code:
handleChange({ target: {name, value} }) {
const domainEmail = '#email.com';
name === 'email'
? this.setState({ [name]: value.replace(domainEmail, '') + domainEmail })
: this.setState({ [name]: value });
}
2) Move out domainEmail to be purely representational, next to the input field. This is how many UI frameworks do it, see for example Bootstrap: https://getbootstrap.com/docs/4.3/components/input-group/
Then when you submit, remember to append the domain. I recommend this approach, it makes it much less fragile, removing many edge cases. It isalso very clear for the user that they should not type their email.
I have 2 forms, in which the validation for a field in the second form is based on the value of a field in the first form. This works as expected when filling in the form top-down. However, when I change the value in the first form, the values object isn't updated in the validation.
My validate-function looks something like this:
const validate = values => {
const errors = {}
if (!values.username) {
errors.username = 'Required'
} else if (values.username.length < values.previous_field.length) {
errors.username = 'Your name can't be shorter than the previous field';
}
return errors;
}
When I change the previous field to a short value after filling in a valid username, the username-field never invalidates.
I decided to rename the forms so then would both have the same name. In this way the values do get updated, and you can use the validation in the way I would like.
Both forms are now named userdata, and the first one has all the validation logic:
#reduxForm({
form: 'userdata',
validate,
asyncValidate,
asyncBlurFields: ['car_licenceplate', 'user_zipcode']
})