I have a Field poll.id that is part of a form that contains multiple parts. Each part is contained in a component with a #reduxForm annotation and each component may have it's own unique validation method.
My problem is that in this case, when the validatePoll returns a validation exception, it doesn't show up at Field level. I wonder if this has to do with the dot syntax of the field.
#reduxForm({
form: 'posteditor',
destroyOnUnmount:false,
})
#autobind
class MyForm extends React.Component {
[...]
pollButton(field){
console.log('poll.id', field);//meta.error = undefined
[...]
}
poll(){
const { poll, visible, syncErrors } = this.props;
return (
<Field
name="poll.id"
value={poll && poll.id}
type="number"
component={this.pollButton}
props={{
visible: visible,
questions: (poll && poll.questions) || []
}}
/>
);
}
}
#reduxForm({
form: 'posteditor',
validate: validatePoll,
destroyOnUnmount:false,
})
#autobind
class PostPoll extends React.Component {
You left out your validation function, but my guess is that you are returning this:
validate(values) {
const errors = {}
if(!values.poll.id) {
errors['poll.id'] = 'Required' // ❌ 👎
}
return errors
}
...and you should be returning this:
validate(values) {
const errors = {}
if(!values.poll.id) {
errors.poll = { id: 'Required' } // ✅ 👍
}
return errors
}
Related
In following up from this question, I am trying to ensure the validation remains and works. However, my combined class does not validate the included fields.
For instance, I have a basic AdminCodeDTO that sepcifies the AdminCode is required, has a valid value (1-999)
import { IsNumber, Min, Max, IsDefined } from '#nestjs/class-validator';
import { ApiProperty, ApiResponseProperty } from '#nestjs/swagger';
export class AdminCodeDTO {
#ApiProperty({
description: 'Sweda Administration Code used for time tracking that is not part of a mantis.',
})
#ApiResponseProperty({ example: 5 })
#IsDefined() #IsNumber() #Min(1) #Max(999) public AdminCode: number;
constructor(AdminCode?: number) {
this.AdminCode = AdminCode;
}
}
Testing this class works, and the validation will return the errors:
import { validate } from '#nestjs/class-validator';
import { ValidationError } from '#nestjs/common';
import { AdminCodeDTO } from './admin-code-dto';
describe('AdminCodeDto', () => {
let TestDTO: AdminCodeDTO;
beforeEach( () => {
TestDTO = new AdminCodeDTO(5);
});
it('should be defined', () => {
expect(TestDTO).toBeDefined();
});
it('should have the AdminCode value set', () => {
expect(TestDTO.AdminCode).toBe(5);
});
it('should allow creation with an empty constructor', () => {
expect(new AdminCodeDTO()).toBeDefined();
});
it('should generate the DTO errors', async () => {
const DTOValidCheck: AdminCodeDTO = new AdminCodeDTO();
const Errors: Array<ValidationError> = await validate(DTOValidCheck);
expect(Errors.length).toBe(1);
expect(Errors[0].constraints['isDefined']).toBe('AdminCode should not be null or undefined');
expect(Errors[0].constraints['isNumber']).toBe('AdminCode must be a number conforming to the specified constraints');
expect(Errors[0].constraints['max']).toBe('AdminCode must not be greater than 999');
expect(Errors[0].constraints['min']).toBe('AdminCode must not be less than 1');
});
});
To then build a simple DTO combining 2 fields to do the testing, I create a description DTO as well, to add that field for this simple example.
import { IsDefined, IsString, MaxLength, MinLength } from '#nestjs/class-validator';
import { ApiProperty, ApiResponseProperty } from '#nestjs/swagger';
export class DescriptionDTO {
#ApiProperty({
description: '',
minLength: 3,
maxLength: 20
})
#ApiResponseProperty({ example: 'Sick Day' })
#IsDefined() #IsString() #MaxLength(20) #MinLength(3) public Description: string;
constructor(Description?: string) {
this.Description = Description;
}
}
I then use the IntersectionType of #nestjs/swagger, to combine the AdminCodeDTO, with a new description field for the payload.
import { IsDefined, IsString, MaxLength, MinLength } from '#nestjs/class-validator';
import { ApiProperty, ApiResponseProperty, IntersectionType} from '#nestjs/swagger';
import { AdminCodeDTO } from './admin-code-dto';
export class AdmininstrationCodesDTO extends IntersectionType(
AdminCodeDTO,
DescriptionDTO
)
{
constructor(AdminCode?: number, Description?: string) {
this.AdminCode = AdminCode;
this.Description = Description;
}
My test however, while all the columns are defined, the validation does not work.
import { AdmininstrationCodesDTO } from './admininstration-codes-dto';
describe('AdmininstrationCodesDTO', () => {
let TestDTO: AdmininstrationCodesDTO;
beforeEach( () => {
TestDTO = new AdmininstrationCodesDTO(77, 'Test Admin Code');
})
it('should be defined', () => {
expect(TestDTO).toBeDefined();
});
it('should be defined when launched without parameters', () => {
expect(new AdmininstrationCodesDTO()).toBeDefined();
})
it.each([
['AdminCode', 77],
['Description', 'Test Admin Code'],
])('should have the proper field {%s} set to be %d', (FieldName, Expected) => {
expect(FieldName in TestDTO).toBe(true);
expect(TestDTO[FieldName]).toBe(Expected);
});
// This test fails as the validation settings are not enforced. Working on any of the DTOs directly though, the validation is confirmed.
it('should generate the DTO errors', async () => {
const TestDTO: AdmininstrationCodesDTO = new AdmininstrationCodesDTO();
const Errors: Array<ValidationError> = await validate(TestDTO, );
expect(Errors.length).toBe(8);
});
});
EDIT: This also causes a problem in my Swagger UI documentation, where this method now prevents my request schemas from showing the data. When I define my fields directly in the DTO (without IntersectionType) the fields show up in the request schema for Swagger. I have the CLI functions enabled in the project.json (NX monorepo).
As found out from your GitHub Issue (thank you for that by the way) you were using #nestjs/class-validator and #nestjs/class-transformer for the validator and transformer packages. #nestjs/mapped-types uses the original class-valdiator and class-transformer packages and these packages use an internal metadata storage device rather than the full Reflect API and metadata storage, so when Nest tried to copy over the metadata from class-validator there was none found because of the use of #nestjs/class-validator, which ended up in having no metadata present for the IntersectionType request
I am making a request using observable. and trying to subcribe the value. But getting error on typescript. any on help me?
I like to do this:
public getCountry(lat,lan):Observable<any>{
return this.http.get(this.googleApi+lat+','+lan+'&sensor=false').subscribe(data => {
return this.genertedData(data);
} );
}
But getting error as follows:
UPDATES
public getCountry(lat,lan):Observable<any>{
return this.http.get(this.googleApi+lat+','+lan+'&sensor=false').map( data => {
data.results.map( array => {
let details = array.address_components.find( obj => obj.types.includes("country") );
this.countryDetails.countryLongName = details.long_name;
this.countryDetails.countryShortName = details.short_name;
})
return this.countryDetails;
})
}
The problem is that your return type states Observable<any>, where as you actually return whatever this.genertedData(data) returns (Hint: Sounds like a typo in your function. Guess it should be called generatedData ?).
Best practice would be to move the http call into a service and subscribe to its returned Observable within your component.
So to speak:
// => service.ts
public getCountryObservable(lat,lan):Observable<any> {
return this.http.get(this.googleApi+lat+','+lan+'&sensor=false');
}
Your component would look something like:
// => component.ts
export class YourComponent {
constructor(public yourService: YourService) {}
getCountry(lat, lan): whateverDataTypeYourGeneratedDataReturns {
this.yourService.getCountryObservable(lat, lan).subscribe((data) => {
this.data = this.generatedData(data);
});
}
}
Since the return type of the function is Observable<any>, I guess it should just return this.http.get(this.googleApi+lat+','+lan+'&sensor=false')
I am having a hard time trying to joing a filterBy with orderBy, on vuejs 2.0, with all research I have found about this subject, as of link on the bottom of my question.
This is my filter, which is working:
// computed() {...
filteredResults() {
var self = this
return self.results
.filter(result => result.name.indexOf(self.filterName) !== -1)
}
A method called in the component:
// methods() {...
customFilter(ev, property, value) {
ev.preventDefault()
this.filterBook = value
}
In the component:
// Inside my component
Name..
And another filter, which works as well:
// computed() {...
orderByResults: function() {
return _.orderBy(this.results, this.sortProperty, this.sortDirection)
}
To comply with my orderBy I have this method:
// methods() {...
sort(ev, property) {
ev.preventDefault()
if (this.sortDirection == 'asc' && this.sortProperty == property ) {
this.sortDirection = 'desc'
} else {
this.sortDirection = 'asc'
}
this.sortProperty = property
}
And to call it I have the following:
// Inside my component
Name..
I have found in the docs how we use this OrderBy, and in this very long conversation how to use filter joint with sort, but I could really not implement it...
Which should be some like this:
filteredThings () {
return this.things
.filter(item => item.title.indexOf('foo') > -1)
.sort((a, b) => a.bar > b.bar ? 1 : -1)
.slice(0, 5)
}
I could not make this work...
I tried in many forms as of:
.sort((self.sortProperty, self.sortDirection) => this.sortDirection == 'asc' && this.sortProperty == property ? this.sortDirection = 'desc' : this.sortDirection = 'asc' )
But still, or it does not compile or it comes with errors, such as:
property not defined (which is defines such as I am using it in the other method)
method of funcion not found (is happens when comment my method sort.. maybe here is what I am missing something)
Thanks for any help!
The ideas of your approach seem valid, but without a full example it's hard to tell what might actually be wrong.
Here's a simple example of sorting and filtering combined. The code can easily be extended e.g. to work with arbitrary fields in the test data. The filtering and sorting is done in the same computed property, based on the parameters set from the outside. Here's a working JSFiddle.
<div id="app">
<div>{{filteredAndSortedData}}</div>
<div>
<input type="text" v-model="filterValue" placeholder="Filter">
<button #click="invertSort()">Sort asc/desc</button>
</div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
testData: [{name:'foo'}, {name:'bar'}, {name:'foobar'}, {name:'test'}],
filterValue: '',
sortAsc: true
};
},
computed: {
filteredAndSortedData() {
// Apply filter first
let result = this.testData;
if (this.filterValue) {
result = result.filter(item => item.name.includes(this.filterValue));
}
// Sort the remaining values
let ascDesc = this.sortAsc ? 1 : -1;
return result.sort((a, b) => ascDesc * a.name.localeCompare(b.name));
}
},
methods: {
invertSort() {
this.sortAsc = !this.sortAsc;
}
}
});
</script>
I have defined a validation rule like this
ko.validation.rules["studentValidation"] = {
validator: (val: any, params: any) => {
return (this.IsInRequiredRangeForStudent(params.DateOfBirth) && val === false);
}
}
IsInRequiredRangeForStudent = (dateOfBirth: any) () => {
//my implementation
}
Here is my ViewModel class, where i consume and apply this rule on an observable
this.isStudent = ko.observable<boolean>(isStudent).extend({
studentValidation: {
message: "Invalid student option!",
params: {
DateOfBirth: this.dateOfBirth()
}
}
});
In my validation rule implementation, I always get params.DateOfBirth as null. What I am doing wrong here?
params.DateOfBirth can be null for several reasons. But firstly I would check one scenario. There is a chance that when you extending isStudent observable, you define validation params assigning value of dateOfBirth observable. But the value is evaluated at the moment of assigning, I don't see the rest of your code but it's highly possible that dateOfBirth observable is null at the moment of assigning to params. So every further check of params.DateOfBirth may return NULL value.
Please try following:
this.isStudent = ko.observable<boolean>(isStudent).extend({
studentValidation: {
message: "Invalid student option!",
params: {
DateOfBirth: this.dateOfBirth
}
}
});
and this:
ko.validation.rules["studentValidation"] = {
validator: (val: any, params: any) => {
return (this.IsInRequiredRangeForStudent(params.DateOfBirth()) && val === false);
}
}
What it changes? It defines params.DateOfBirth as function (not a value), so you can evaluate its value on every validation call.
I have a Fields component that I am trying to use sometimes by itself and sometimes from inside a FieldArray component. I have added a snippet below with a simplified model.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { reduxForm, Fields, FieldArray, reducer as formReducer } from 'redux-form';
const reducers = {
form: formReducer
};
const reducer = combineReducers(reducers);
const store = createStore(reducer);
const renderSharedComponent = (fields) => {
console.log(fields);
return (<div>Shared Component</div>);
};
const renderHashes = ({ fields }) => (
<div>
{
fields.map((field) => (
<Fields
key={ field }
names={ [`${field}.value`, `${field}.valueIsRegex`] }
component={ renderSharedComponent }
/>
))
}
</div>
);
const ReactComponent = () => (
<div>
<FieldArray
name="hashes"
component={ renderHashes }
/>
<Fields
names={ ['value', 'valueIsRegex'] }
component={ renderSharedComponent }
/>
</div>
);
const ReduxForm = reduxForm({
form: 'default',
initialValues: {
hashes: [{}]
}
})(ReactComponent);
ReactDOM.render((
<div>
<Provider store={ store }>
<ReduxForm />
</Provider>
</div>
), document.getElementById('content'));
When I use the Fields component by itself, the fields argument from inside renderSharedComponent has the following form:
{
value: { input: {...}, meta: {...} },
valueIsRegex: { input: {...}, meta: {...} },
names: [ 'value' , 'valueIsRegex' ]
}
When I use the Fields component inside a FieldArray component, the fields argument from inside renderSharedComponent has the following form:
{
hashes: [
{
value: { input: {...}, meta: {...} },
valueIsRegex: { input: {...}, meta: {...} }
}
],
names: [ 'hashes[0].value' , 'hashes[0].valueIsRegex' ]
}
If I will be using the Fields component inside a FieldArray component with a different name (let's say paths) the names property will change accordingly (eg. names: [ 'paths[0].value' , 'paths[0].valueIsRegex' ]).
I am trying to get the value and valueIsRegex objects in a generic way that will support any of the cases I presented above.
Right now I have created a function where I use a RegEx to determine the fields. But I was wondering if anyone knows a better way to do this (maybe there is a Redux Form util that maybe I missed when reading the documentation).
having the same problem. It may be that the idiomatic way is to use the formValueSelector function. But that way its a bit more boilerplate, as you have to pass the selector (as far as I understood it) all the way down through the form.
Personally I did the regexp-based function as well. Here it is:
/**
* Converts path expression string to array
* #param {string} pathExpr path string like "bindings[0][2].labels[0].title"
* #param {Array<string|int>} array path like ['bindings', 0, 2, 'labels', 0, 'title']
*/
function breakPath(pathExpr) {
return pathExpr
.split(/\]\.|\]\[|\[|\]|\./)
.filter(x => x.length > 0)
.map(x => isNaN(parseInt(x)) ? x : parseInt(x));
}
/**
* Executes path expression on the object
* #param {string} pathExpr – path string like "bindings[0][2].labels[0].title"
* #param {Object|Array} obj - object or array value
* #return {mixed} a value lying in expression path
* #example
* ```
* execPath('books[0].title', {books: [{title: 'foo'}]})
* // yields
* 'foo'
* ```
*/
function execPath(pathExpr, obj) {
const path = breakPath(pathExpr);
if (path.length < 1) {
return obj;
}
return path.reduce(function(obj, pathPart) {
return obj[pathPart];
}, obj);
}
/**
* A generic GroupLabelField that relies on valueBasePath
*/
const GroupLabelField = (props) => {
const groupData = execPath(props.valueBasePath, props);
return (
<div className={`label__content label__content_icon_${groupData.objectType.input.value}`}>
<span className="label__remove"
onClick={(e) => { props.handleRemove(); e.stopPropagation(); }}
>
<i className="material-icons material-icons_12 material-icons_top"></i>
</span>
<span className="label__text">{groupData.title.input.value}</span>
</div>
);
};
redux-form has a hidden utility function which is useful here, but I don't know if you can rely on it's availability in future versions:
import structure from "redux-form/lib/structure/plain";
function RenderRow({ names, ...props }) {
const fields = {};
names.forEach(n => (fields[n] = structure.getIn(props, n)));
}
See also this github issue