I'm using VueJS2 with vuelidate library. I can validate the fields based on the validation objects. The validation will execute during computed time. But My validations objects is fixed, instead of dynamic. I have some fields will hide based on the selection.
import { validationMixin } from 'vuelidate'
import { required, maxLength, email } from 'vuelidate/lib/validators'
export default {
mixins: [validationMixin],
validations: {
company_name: { required },
company_position_title: { required }
},
methods: {
submit(){
this.$v.touch();
if(this.$v.$invalid == false){
// All validation fields success
}
}
}
}
HTML
<v-select
label="Who are you?"
v-model="select" // can be 'company' or 'others'
:items="items"
:error-messages="selectErrors"
#change="$v.select.$touch();resetInfoFields();"
#blur="$v.select.$touch()"
required
></v-select>
<v-text-field
label="Company Name"
v-model="company_name"
:error-messages="companyNameErrors"
:counter="150"
#input="$v.companyName.$touch()"
#blur="$v.companyName.$touch()"
v-show="select == 'Company'"
></v-text-field>
<v-text-field
label="Company Position Title"
v-model="company_position_title"
:error-messages="companyPositionErrors"
:counter="150"
#input="$v.companyPosition.$touch()"
#blur="$v.companyPosition.$touch()"
v-show="select == 'Company'"
></v-text-field>
<v-btn #click="submit">submit</v-btn>
Problem
When I select 'other' option and click submit, the this.$v.$invalid is still true. It should be false as there is no validation fields required.
When I select 'company', that two fields must required and validated.
you need a dynamic validation schema
validations () {
if (!this.select === 'company') {
return {
company_name: { required },
company_position_title: { required }
}
}
}
More info: Dynamic validation schema
Another way is using requiredIf
itemtocheck: {
requiredIf: requiredIf(function () {
return this.myitem !== 'somevalue'
}),
minLength: minLength(2) },
Related
I code a Gatsby app with a Main page and two components. The value from a select form will be used to query a Postgresql database through a graphql query.
What I can already do: in the form component, I get the value from the select menu and pass it from this child component to the parent (the main page). In the data component, I can query the database with graphql and get the results with hardcoded values.
What I can't do yet: get the value from the select component to the data component and use it in my graphql query.
I tried different ways to get the value without success using this.props.value1 or this.state.value1. I also tested a simple component to make sure I could get the value from the parent to a child component and it worked seamlessly. So it's the way I try to import the value in a querying component that is the problem.
**//Data component**
let val = 88 //for hardcoded test. That works.
const DataPage = () => {
const data = useStaticQuery(query)
return (
<div>
<p>From Postgres: {data.postgres.allEstivalsList[val].nbr}</p>
</div>
)
}
const query = graphql`
{
postgres {
allAveragesList {
avg
}
allEstivalsList {
year
nbr
}
}
}
`;
export default DataPage;
**//Main page**
export default class App extends React.Component {
constructor(props) {
super(props);
}
state = {
value1: null,
// other values
}
render() {
return (
<div>
<p>Get state in main page: {this.state.value1}</p>
<DataPage val = {this.state.value1} />
<SelectForm clickHandler={y => { this.setState({ value1: y }); }} />
</div>
)
}
}
**//Form component**
export default class IndexPage extends React.Component {
state = {
value1: null,
}
handleClick = () => {
this.props.clickHandler(this.state.value1);
this.setState(prevState => {
return { value1: prevState.value1 };
});
};
render() {
return (
<Formlayout>
<p>Test -- Value for the selected year: {this.state.value1}</p>
<select onChange = {(e) => this.setState({ value1: e.target.value })}>
<option value="">-- Year --</option>
<option value="1">1901</option>
<option value="2">1902</option>
<option value="3">1903</option>
</select>
<button onClick={this.handleClick}>Go!</button>
</Formlayout>
)
}
}
I'd appreciate to get some directions to get the select value in the data component. As my test variable val is effectively working when used in the query, what I'd like to achieve is to apply to that variable the state from the component. And that's where I'm stuck right now.
The GraphQL query is executed at the build time, not at runtime. Have a look at the docs, it's possible to query data at build and at runtime.
https://www.gatsbyjs.org/docs/client-data-fetching/
// edit
Normally you would pre generate all (static) content pages - e.g. if you have data for 50 years, you let gatsby build those 50 pages and then you could navigate by path to the page.
If you want to pass a variable to the query, check the docs: https://www.gatsbyjs.org/docs/graphql-reference/#query-variables
query GetBlogPosts(
$limit: Int, $filter: MarkdownRemarkFilterInput, $sort: MarkdownRemarkSortInput
) {
allMarkdownRemark(
limit: $limit,
filter: $filter,
sort: $sort
) {
edges {
node {
frontmatter {
title
date(formatString: "dddd DD MMMM YYYY")
}
}
}
}
}
react-select just upgraded to 2.0.0 so google results on the first three pages are all about older versions, even the official document, and none of them helped.
My select box can show all options correctly, but redux form won't pick up the value, with the warning: Warning: A component is changing a controlled input of type hidden to be uncontrolled.
I wonder what have I missed here...
Form component:
<Field
name="residentialAddress"
label = "Residential Address"
type="select"
component={AddressField}
validate={required}
/>
Component
export class AddressField extends Component {
searchAddress = input => {
let options = []
return myPromise(input)
.then(suggestions => {
options = suggestions.map(suggestion =>
({
label: suggestion.label,
data: suggestion.value
})
)
return options;
}
).catch(
error => {
return options = [{ label: "Auto fetching failed, please enter your address manually", value: "", isDisabled: true }];
}
);
};
render() {
const {
input,
label,
meta: { touched, error },
type
} = this.props;
return(
<FormGroup>
<ControlLabel>{label}</ControlLabel>
<Async
{...input}
placeholder={label}
isClearable={true}
getOptionValue={(option) => option.residentialAddress}
onChange = { value => input.onChange(value.data) }
loadOptions={this.searchAddress}
/>
{ touched && error && <span>{error}</span> }
</FormGroup>
)
}
Solution: Simply remove the {...input} in <Async>.
Unlike regular custom Field component where we need to pass in {input}, the react-select Async component seems to take care of itself very well and doesn't require any intervene. Someone may explain it in a more professional way perhaps...
Also worth mention for those who come across this question:
loadOptions with promise used to require object {options: options} as return type. Now it changes to just array (options as in my code). But I didn't find any document that mentions this one.
Hope this could help.
I'm currently working on a project that uses redux-form fields. We make use of the react-google-autocomplete component to allow users to enter an address in a similar fashion to if they were typing it in Google Maps.
Currently, we strip out the name of the location (if there is one) and just store the address. (So if I typed in "The White House", and selected the suggestion of "The White House, Pennsylvania Avenue Northwest, Washington, DC", we actually end up storing "1600 Pennsylvania Avenue Northwest, Washington, DC".
import React, { PropTypes } from 'react';
import { Field } from 'redux-form';
import Autocomplete from 'react-google-autocomplete';
function emulateTabPress(currentEl) {
const formEls = Array.from(currentEl.form.elements);
const currentIdx = formEls.findIndex(el => el === currentEl);
formEls[currentIdx + 1].focus();
}
function getFormattedGoogleAddress(googleParams) {
return googleParams.formatted_address || googleParams.name;
}
function renderGoogleAutoComplete(props) {
return (
<Autocomplete
type="text"
name="location"
onPlaceSelected={
param => props.input.onChange(getFormattedGoogleAddress(param))
}
types={[]}
value={props.input.value}
onChange={newValue => props.input.onChange(newValue)}
onKeyPress={event => {
if (event.key === 'Enter') {
event.preventDefault();
emulateTabPress(event.target);
}
}}
/>
);
}
renderGoogleAutoComplete.propTypes = {
input: PropTypes.shape({
value: PropTypes.string,
onChange: PropTypes.func
})
};
function AutocompleteLocation({ name, required }) {
return (
<Field
name={name}
required={required}
component={renderGoogleAutoComplete}
/>
);
}
AutocompleteLocation.propTypes = {
name: PropTypes.string.isRequired,
required: PropTypes.bool
};
export default AutocompleteLocation;
What I WANT to do, is store three separate pieces of information
The address (googleParam.formatted_address)
The name of the location (googleParam.name)
The Google ID for the location (googleParam.id)
I've written code that stores this as an object and used that as the value in my component, but then when I try to use any of the values from the store later on, it just shows as "object object"
Any suggestions on how to get these values into discrete data elements?
Please Refer to my plunkr
I've been playing around with the new Angular 2 RC and I think I have figured out how the form validation works.
First I build 2 objects called defaultValidationMessages and formDefinition
private defaultValidationMessages: { [id: string]: string };
formDefinition: {
[fieldname: string]:
{
displayName: string,
placeholder: string,
currentErrorMessage: string,
customValidationMessages: { [errorKey: string]: string }
defaultValidators: ValidatorFn,
defaultValue: any
}
};
Then I load up those objects with the default validators and field information. and build the ControlGroup from the formDefinition object.
this.defaultValidationMessages = {
'required': '{displayName} is required',
'minlength': '{displayName} must be at least {minlength} characters',
'maxlength': '{displayName} cannot exceed {maxlength} characters',
'pattern': '{displayName} is not valid'
}
this.formDefinition = {
'name': {
displayName: 'Name',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose(
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(50)
]),
defaultValue: this.person.name
},
'isEmployee': {
displayName: 'Is Employee',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose([]),
defaultValue: this.person.isEmployee
},
'employeeId': {
displayName: 'Employee Id',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: { 'pattern': '{displayName} must be 5 numerical digits' },
defaultValidators: Validators.compose(
[
Validators.pattern((/\d{5}/).source)
]),
defaultValue: this.person.employeeId
}
}
this.personForm = this.formBuilder.group({});
for (var v in this.formDefinition) {
this.personForm.addControl(v, new Control(this.formDefinition[v].defaultValue, this.formDefinition[v].defaultValidators));
}
this.personForm.valueChanges
.map(value => {
return value;
})
.subscribe(data => this.onValueChanged(data));
Using a technique that I learned from Deborah Kurata's ng-conf 2016 session I bind a method to the ControlGroups valueChanges event.
By defining sets of default validators on each control it allows the control to dynamically append new validators to it based on future action. And then clearing back to the default validators later.
Issue I still have.
I was having an issue getting my typescript intellisense to import the ValidatorFn type. I found it here but I don't think I'm suppose to access it like this:
import { ValidatorFn } from '../../../node_modules/#angular/common/src/forms/directives/validators'
I also had to reset the form by setting some internal members. Is there a better way to reset the form? see below:
(<any> this.programForm.controls[v])._touched = false;
(<any> this.programForm.controls[v])._dirty = false;
(<any> this.programForm.controls[v])._pristine = true;
Please look at my plunk and let me know if there is a better way to handle model driven dynamic form validation?
My import string looks like this and it isn't marked as an error.
import { ValidatorFn } from '#angular/common/src/forms/directives/validators';
And some info about the reset form issue. There is not proper reset feature available yet, but a workaround exists. I've found it in docs.
You need a component field
active: true;
and you need to check it in your form tag:
<form *ngIf="active">
After that you should change your personFormSubmit() method to:
personFormSubmit() {
this.person = new Person();
this.active = false;
setTimeout(()=> {
this.active=true;
this.changeDetectorRef.detectChanges();
alert("Form submitted and reset.");
}, 0);
}
I tried this solution with you plnkr example and seems that it works.
I need to make certain form fields required or not based on the value of other fields. The built-in RequiredValidator directive doesn't seem to support this, so I created my own directive:
#Directive({
selector: '[myRequired][ngControl]',
providers: [new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => MyRequiredValidator), multi: true })]
})
class MyRequiredValidator {
#Input('myRequired') required: boolean;
validate(control: AbstractControl): { [key: string]: any } {
return this.required && !control.value
? { myRequired: true }
: null;
}
}
Sample usage:
<form>
<p><label><input type="checkbox" [(ngModel)]="isNameRequired"> Is Name Required?</label></p>
<p><label>Name: <input type="text" [myRequired]="isNameRequired" #nameControl="ngForm" ngControl="name" [(ngModel)]="name"></label></p>
<p *ngIf="nameControl.control?.hasError('myRequired')">This field is required.</p>
</form>
This works fine if the user first toggles the check box and then types or erases text in the text box. However, if the user toggles the check box while the text box is blank, then the validation message doesn't update appropriately.
How can I modify MyRequiredValidator to trigger validation when its required property is changed?
Note: I'm looking for a solution that only involves changes to MyRequiredValidator. I want to avoid adding any logic to the App component.
Plunker: https://plnkr.co/edit/ExBdzh6nVHrcm51rQ5Fi?p=preview
I would use something like that:
#Directive({
selector: '[myRequired][ngControl]',
providers: [new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => MyRequiredValidator), multi: true })]
})
class MyRequiredValidator {
#Input('myRequired') required: boolean;
ngOnChanges() {
// Called when required is updated
if (this.control) {
this.control.updateValueAndValidity();
}
}
validate(control: AbstractControl): { [key: string]: any } {
this.control = control;
return this.required && !control.value
? { myRequired: true }
: null;
}
}
See this plunkr: https://plnkr.co/edit/14jDdUj1rdzAaLEBaB9G?p=preview.