Function Query.where() requires a valid third argument, but it was undefined when trying to view the page - google-cloud-firestore

I'm trying to implement rating system in my shopping app, but received error console when trying to open the page.
The error on console are:
Function Query.where() requires a valid third argument, but it was undefined. - it points to:
this.stars = this.starService.getProductStars(this.movieId)
AND
const starsRef = this.afs.collection('stars', ref => ref.where('movieId', '==', movieId));
Below is my code:
rating.page.html: (where I put my 2 of components which are TestRate and Star-Review)
<ion-content>
<app-testrate></app-testrate>
<app-star-review-component></app-star-review-component>
</ion-content>
testrate.component.html:
<div *ngIf="movie | async as m">
<h1>
{{m.title}}
</h1>
<img [src]="m.image" width="100px">
<p>
{{m.plot}}
</p>
<star-review [movieId]="movieId" [userId]="userId"></star-review>
</div>
testrate.component.ts:
export class TestrateComponent implements OnInit {
userDoc: AngularFirestoreDocument<any>;
movieDoc: AngularFirestoreDocument<any>;
user: Observable<any>;
movie: Observable<any>;
constructor(private afs: AngularFirestore) { }
ngOnInit() {
this.userDoc = this.afs.doc('users/test-user-3')
this.movieDoc = this.afs.doc('movies/battlefield-earth')
this.movie = this.movieDoc.valueChanges()
this.user = this.userDoc.valueChanges()
}
get movieId() {
return this.movieDoc.ref.id
}
get userId() {
return this.userDoc.ref.id
}
}
star-review.component.html:
<h3>Average Rating</h3>
{{ avgRating | async }}
<h3>Reviews</h3>
<div *ngFor="let star of stars | async">
{{ star.userId }} gave {{ star.movieId }} {{ star.value }} stars
</div>
<h3>Post your Review</h3>
<fieldset class="rating">
<ng-container *ngFor="let num of [5, 4, 3, 2, 1]">
full star
<input (click)="starHandler(num)"
[id]="'star'+num"
[value]="num-0.5"
name="rating"
type="radio" />
<label class="full" [for]="'star'+num"></label>
half star
<input (click)="starHandler(num-0.5)"
[value]="num-0.5"
[id]="'halfstar'+num"
name="rating"
type="radio" />
<label class="half" [for]="'halfstar'+num"></label>
</ng-container>
</fieldset>
star-review.component.ts:
export class StarReviewComponentComponent implements OnInit {
#Input() userId;
#Input() movieId;
stars: Observable<any>;
avgRating: Observable<any>;
constructor(private starService: StarService) { }
ngOnInit() {
this.stars = this.starService.getProductStars(this.movieId)
this.avgRating = this.stars.pipe(map(arr => {
const ratings = arr.map(v => v.value)
return ratings.length ? ratings.reduce((total, val) => total + val) / arr.length : 'not reviewed'
}))
}
starHandler(value) {
this.starService.setStar(this.userId, this.movieId, value)
}
}
star.service.ts:
export class StarService {
constructor(private afs: AngularFirestore) { }
// Star reviews that belong to a user
getUserStars(userId) {
const starsRef = this.afs.collection('stars', ref => ref.where('userId', '==', userId));
return starsRef.valueChanges();
}
// Get all stars that belog to a Product
getProductStars(movieId) {
const starsRef = this.afs.collection('stars', ref => ref.where('movieId', '==', movieId));
return starsRef.valueChanges();
}
// Create or update star
setStar(userId, movieId, value) {
// Star document data
const star: Star = { userId, movieId, value };
// Custom doc ID for relationship
const starPath = `stars/${star.userId}_${star.movieId}`;
// Set the data, return the promise
return this.afs.doc(starPath).set(star)
}
}

Related

sending Angular 6 form including checkbox values not working with template driven forms

I'm trying to pass form values including checkboxes in angular 6 forms using formbuilder but I'm unable to read the value from checkbox. I am getting all the values from all the other input fields but only checkbox is not responding Here is my code:
<form [formGroup]="myGroup" (submit)="submit(myGroup.value)">
<div class="row">
<div class="col-sm-4" *ngFor="let info of myGroup.controls['myInfo'].controls; let i = index">
<label for="{{labelValue[i].name}}"> {{labelValue[i].label}}
<input type="{{labelValue[i].type}}" class="{{labelValue[i].class}}" [formControl]="info">
</label>
</div>
</div>
<div class="row">
<button class="form-control btn-sub" type=”submit”>
Submit Details
</button>
</div>
My component class:
import { ProposalService, CustomerDetails, ProposalNumber } from 'src/app/Services/Proposal-service/proposal.service';
export interface InputType{
name:string;
type: string;
label: string;
class:string;
}
export class ProposalComponent implements OnInit {
public labelValue: InputType[] = [
{name:"fname",type:"text",label:"First Name", class:"form-control"},
{name:"form60",type:"checkbox",label:"Is Collection Of form 60", class:"form-control"},
{name:"eia-num",type:"number",label:"EIA Number", class:"form-control"}
];
title = "Customer Details";
details: Observable<CustomerDetails>;
pNumber: ProposalNumber ;
public information: CustomerDetails[] = [
{name:"First Name", value:""},//
{name:"IsCollectionOfform60", value:true},
{name:"EIA Number", value:""}
];
myGroup : FormGroup;
constructor(private formBuilder: FormBuilder,
private _proposalService: ProposalService) { }
ngOnInit() {
this.myGroup = this.formBuilder.group({
myInfo: this.constructFormArray()
});
this.pNumber = <ProposalNumber>{proposalNumber: 0 ,message:"", status: ""};
}
constructFormArray()
{
const arr = this.information.map(cat => {
return this.formBuilder.control(cat.value);
});
return this.formBuilder.array(arr);
}
submit(form){
//this.loading = true;
console.log(form);
let mySelectedAddon = form.myInfo.map((currentValue,i)=> {
return { "name" : this.information[i].name , "value" : currentValue}
}
);
console.log(mySelectedAddon);
this._proposalService.loadCustomer(mySelectedAddon).subscribe((res: ProposalNumber) =>{
//this.loading = false;
console.log(res);
this.pNumber.proposalNumber = res.proposalNumber;
this.pNumber.message = res.message;
console.log(this.pNumber.proposalNumber);
return this.myGroup.value;
});
}
}
You need to use the 'change' event and pass the respective input value and event to a method onChange where you check if it's checked, then add the respective value to the formarray, if it's unchecked, remove the chosen email from the form array.
You can refer the below link:
https://stackblitz.com/edit/angular-rskaug?file=src%2Fapp%2Fapp.component.ts
Above example is useful to get the values of checkbox dynamically.

How to prevent repeating code of form validation

I created the form for multiple inputs, where the specific input data shall be validated at the time of data entry and once again for all data just before the submission of the form to the backend.
The conditions to submit: all fields are mandatory and the data is valid.
My program works, but I don't like that I'm repeating the validation code in 2 places: in ErrorOutput and hadleSubmit.
In ErrorOutput I check the data and, if necessary, display an error message.
In handleSubmit I just check the data without displaying of error message and if all data is valid, I confirm submitting.
How can I improve my example to prevent the repetition of this code, but the data validation was also at the time of data entry and before submission?
import React from 'react'
import { render } from 'react-dom'
const ErrorOutput = props => {
let name = props.name
let inputValue = props.case
let submit = props.submit
// Data validation
if (name === 'firstName') {
if (!inputValue.match(/^[a-zA-Z]+$/) && inputValue.length > 0) {
return <span>Letters only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
if (name === 'telNo') {
if(!inputValue.match(/^[0-9]+$/) && inputValue.length > 0) {
return <span>Numbers only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false
}
}
handleSubmit(e){
e.preventDefault()
let submit = true
let error = true
const { firstName, telNo } = this.state
this.setState ({submit: submit})
// Repeat the data validation before submission
if (firstName === '' || !firstName.match(/^[a-zA-Z]+$/)) {
error = true
} else if (telNo === '' || !telNo.match(/^[0-9]+$/)) {
error = true
} else {
error = false
}
// Submited if all data is valid
if (!error) {
// send data
return alert('Success!')
}
}
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>
First name:
</label>
<input
type='text'
name ='firstName'
value = {this.state.firstName}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.firstName} name={'firstName'} submit = {this.state.submit} />
</div>
<div>
<label>
Phone number:
</label>
<input
type='tel'
name ='telNo'
value = {this.state.telNo}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.telNo} name={'telNo'} submit = {this.state.submit} />
</div>
<button>
Submit
</button>
</form>
)
}
}
render(
<App />,
document.getElementById('root')
)
You could extract a FormItem component:
class FormItem extends React.Component {
render() {
return (
<div>
<label>
{this.props.label}
</label>
<input
{...this.props.input}
/>
<ErrorOutput
case={this.props.input.value}
name={this.props.input.name}
submit={this.props.onSubmit}
/>
</div>
);
}
}
and use it in your App:
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<FormItem label='First name:' input={{
type: 'text'
name: 'firstName'
value: this.state.firstName,
onChange: this.handleValidation.bind(this)
}}
onSubmit={this.state.submit}
/>
<FormItem label='Phone number:' input={{
type:'tel'
name :'telNo'
value : {this.state.telNo}
onChange : {this.handleValidation.bind(this)}
}}
onSubmit={this.state.submit}
/>
<button>
Submit
</button>
</form>
)
}
this is where libraries like react-final-form and redux-form become handy.
UPD
ErrorOutput component should not validate anything, it is not a responsibility of a component. Instead, you could validate your values on inputs blur event and before submit:
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false,
errors: {},
invalid: false,
}
}
handleSubmit(e){
e.preventDefault()
if (this.validate()) {
// handle error
} else {
// submit
}
}
validate = () => {
const { firstName, telNo } = this.state
const errors = {}
let invalid = false;
if (firstName === '' || !firstName.match(/^[a-zA-Z]+$/)) {
errors.firstName = 'first name is required'
invalid = true;
} else if (telNo === '' || !telNo.match(/^[0-9]+$/)) {
telNo.telNo = 'telNo is required'
invalid = true;
}
this.setState({
invalid,
errors,
})
return invalid;
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<FormItem label='First name:' input={{
type: 'text',
name: 'firstName',
value: this.state.firstName,
onChange: e => this.setState({ firstName: e.target.value }),
onBlur: () => this.validate(),
}}
/>
<FormItem label='Phone number:' input={{
type: 'tel',
name: 'telNo',
value: this.state.telNo,
onChange: e => this.setState({ telNo: e.target.value }),
onBlur: () => this.validate(),
}}
/>
<button>
Submit
</button>
</form>
)
}
}
and FormItem and ErrorOutput:
const ErrorOutput = ({ error }) => <span>{error}</span>
class FormItem extends React.Component {
render() {
return (
<div>
<label>
{this.props.label}
</label>
<input
{...this.props.input}
/>
{this.props.error && <ErrorOutput error={this.props.error} />}
</div>
);
}
}
const ErrorOutput = ({ errorText }) => <span>{errorText}</span>;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
telNo: "",
submit: false,
errors: {} //Add errors object to the state.
};
}
handleSubmit(e) {
e.preventDefault();
const errors = this.validateData();
if (Object.keys(errors).length === 0) {
alert("Success");
}
//else errors exist
this.setState({ errors });
}
validateData = () => {
let errors = {};
const { firstName, telNo } = this.state; // read the values to validate
if (firstName.length === 0) {
errors.firstName = "Required";
} else if (firstName.length > 0 && !firstName.match(/^[a-zA-Z]+$/)) {
errors.firstName = "Letters only";
}
if (telNo.length === 0) {
errors.telNo = "Required";
} else if (telNo.length > 0 && !telNo.match(/^[0-9]+$/)) {
errors.telNo = "Numbers only";
}
return errors;
};
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
const { errors } = this.state; // read errors from the state
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>First name:</label>
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleValidation.bind(this)}
/>
{errors.firstName && <ErrorOutput errorText={errors.firstName} />}
</div>
<div>
<label>Phone number:</label>
<input
type="tel"
name="telNo"
value={this.state.telNo}
onChange={this.handleValidation.bind(this)}
/>
{errors.telNo && <ErrorOutput errorText={errors.telNo} />}
</div>
<button>Submit</button>
</form>
);
}
}
render(<App />, document.getElementById("root"));

Load form value from state

First I show a list of transactions, when a user selects a single transaction a new page is opened with the transaction ID in the URL. On this page are details of the transaction displayed.
The code below is just the details page. It shows all the right details.
One of the details is a list of 0 or more tags, I'd like to be able to edit the list of tags and save the result.
At this point, I always end up with a clean Input field and I do not understand how to populate this field with the existing transaction['tags'] data.
It seems that the transaction['tags'] is not initialized until the page is rendered, I cannot use it in the constructor or in the componentDidMount.
What I expect is that the transaction object as stated in the mapStateToProps is available and I can change the line in the constructor from: this.state = {value: ''}; to this.state = {value: transaction['tags']}
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchTransaction } from '../actions';
class TransactionsIndex extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
const { _key } = this.props.match.params;
this.props.fetchTransaction(_key);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
const { transaction } = this.props;
if (!transaction) {
return <div>Loading...</div>;
}
let tags = null;
tags =
<div>
<form onSubmit={this.handleSaveTagsclick}>
<input type="text" value={this.state.value} onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
</div>
// console.log(transaction['tags']);
return (
<div className="container">
<div className="well">
<div>Transactiedatum: { transaction["Transactiedatum"] }</div>
<div>Valutacode: { transaction["Valutacode"] }</div>
<div>CreditDebet: { transaction["CreditDebet"] }</div>
<div>Bedrag: { transaction["Bedrag"] }</div>
<div>Tegenrekeningnummer: { transaction["Tegenrekeningnummer"] }</div>
<div>Tegenrekeninghouder: { transaction["Tegenrekeninghouder"] }</div>
<div>Valutadatum: { transaction["Valutadatum"] }</div>
<div>Betaalwijze: { transaction["Betaalwijze"] }</div>
<div>Omschrijving: { transaction["Omschrijving"] }</div>
<div>Type betaling: { transaction["Type betaling"] }</div>
<div>Machtigingsnummer: { transaction["Machtigingsnummer"] }</div>
<div>Incassant ID: { transaction["Incassant ID"] }</div>
<div>Adres: { transaction["Adres"] }</div>
<div>Status: { transaction["status"] }</div>
<div>Created: { transaction["created"] }</div>
{tags}
</div>
<Link to="/"><button type="button" className="btn btn-default">Back</button></Link>
</div>
);
};
}
function mapStateToProps({ transactions }) {
// console.log('transactions_selectedTransaction: ' + transactions['selectedTransaction']);
return { transaction: transactions['selectedTransaction'] };
}
export default connect(mapStateToProps, { fetchTransaction })(TransactionsIndex);
I found this but it did not help me: Redux-form: Set form values from state
and this: How to get state / value from form component?

ReactJS Semantic ui - Cannot type into form when value attribute exists

I am following a ReactJS tutorial to set up a login form. Semantic ui is used and imported. The email and password are passed into the value attribute inside the form. When this happens, I cannot type anything into the form. As soon as I remove it, I can type information in but I assume it won't get passed into anywhere.
Cannot seem to find this issues anywhere else. Has anyone experienced this issue before?
import React from 'react';
import PropTypes from 'prop-types';
import { Form, Button } from 'semantic-ui-react';
import Validator from 'validator';
import InlineError from '../messages/InlineError';
class LoginForm extends React.Component {
state = {
data: {
email: "",
password: ""
},
loading: false,
errors: {}
};
//... is called spread
onChange = e => this.setState({
data: {...this.state.data, [e.target.name]: e.target.value }
});
//() means function takes no params
onSubmit = () => {
const errors = this.validate(this.state.data);
this.setState({errors}); //if there are errors, display them
if(Object.keys(errors).length === 0){
this.props.submit(this.state.data);
}
};
validate = (data) => {
const errors = {};
if(!Validator.isEmail(data.email))
errors.email = "Invalid email";
if(!data.password)
errors.password = "Can't be blank";
return errors;
};
render() {
const { data, errors } = this.state; // import variables into html
return (
<div>
<Form onSubmit={ this.onSubmit }>
<Form.Field error={!!errors.email}>
<label htmlFor="email">Email</label>
<input type="email"
id="email"
placeholder="example#abc.com"
value={ data.email }
onChange={ this.onChange }/>
{errors.email && <InlineError text={errors.email}/>}
</Form.Field>
<Form.Field error={!!errors.email}>
<label htmlFor="password">Password</label>
<input type="password"
id="password"
value={ data.password }
onChange={this.onChange}/>
{errors.password && <InlineError text={errors.password}/>}
</Form.Field>
<Button primary>Login</Button>
</Form>
</div>
);
}
}
LoginForm.propTypes = {
submit: PropTypes.func.isRequired
};
export default LoginForm;
tutorial: https://www.youtube.com/watch?v=NO2DaxhoWHk&t=879s
onChange = e => this.setState({
data: {...this.state.data, [e.target.name]: e.target.value }
});
This function is setting the state to a variable that shares the name of your input field. Hence e.target.name. But your input fields do not have a name attribute.
You can fix that with:
import React from 'react';
import PropTypes from 'prop-types';
import { Form, Button } from 'semantic-ui-react';
import Validator from 'validator';
import InlineError from '../messages/InlineError';
class LoginForm extends React.Component {
state = {
data: {
email: "",
password: ""
},
loading: false,
errors: {}
};
//... is called spread
onChange = e => this.setState({
data: {...this.state.data, [e.target.name]: e.target.value }
});
//() means function takes no params
onSubmit = () => {
const errors = this.validate(this.state.data);
this.setState({errors}); //if there are errors, display them
if(Object.keys(errors).length === 0){
this.props.submit(this.state.data);
}
};
validate = (data) => {
const errors = {};
if(!Validator.isEmail(data.email))
errors.email = "Invalid email";
if(!data.password)
errors.password = "Can't be blank";
return errors;
};
render() {
const { data, errors } = this.state; // import variables into html
return (
<div>
<Form onSubmit={ this.onSubmit }>
<Form.Field error={!!errors.email}>
<label htmlFor="email">Email</label>
<input type="email"
id="email"
name="email"
placeholder="example#abc.com"
value={ data.email }
onChange={ this.onChange }/>
{errors.email && <InlineError text={errors.email}/>}
</Form.Field>
<Form.Field error={!!errors.email}>
<label htmlFor="password">Password</label>
<input type="password"
id="password"
name="password"
value={ data.password }
onChange={this.onChange}/>
{errors.password && <InlineError text={errors.password}/>}
</Form.Field>
<Button primary>Login</Button>
</Form>
</div>
);
}
}
LoginForm.propTypes = {
submit: PropTypes.func.isRequired
};
export default LoginForm;

React Form validation displaying error

I am using React-Validation-Mixin together with Joi and Joi-Validation-Strategy to do some validations on a React Step/Wizard Form.
I have a parent FormStart Element that receives the state of its FormStep children through props.
The validation correctly signals that the input is required, but when I write a correct number in the browser (5 numbers as in PLZ/ZIP-Code), it will still signal that the input is invalid, even though the zip state shows a correct 5-digit number, so the next button never takes me to the next Form step.
class FormStart extends Component {
constructor(props) {
super(props);
this.state = {
step: 1,
zip: ""
}
this.goToNext = this.goToNext.bind(this);
}
goToNext() {
const { step } = this.state;
if (step !== 10) {
this.setState({ step: step + 1 });
if (step == 9) {
const values = {
zip: this.state.zip,
};
console.log(values);
// submit `values` to the server here.
}
}
}
handleChange(field) {
return (evt) => this.setState({ [field]: evt.target.value });
}
render(){
switch (this.state.step) {
case 1:
return <FormButton
onSubmit={this.goToNext}
/>;
//omitting the other 8 cases
case 9:
return <FormStep7
onSubmit={this.goToNext}
zip={this.state.zip}
onZipChange={this.handleChange('zip')}
/>;
case 10:
return <FormSuccess/>;
}
}
}
export default FormStart;
The React console shows that the zip state is correctly changed, and the Validation object also receives the same correct 5-digit zip and still holds the correct value onBlur.
class FormStep7 extends Component {
constructor(props) {
super(props);
this.validatorTypes = {
PLZ: Joi.number().min(5).max(5).required().label('PLZ').options({
language: {
number: {
base: 'wird benötigt',
min: 'muss {{limit}} Nummern enthalten',
max: 'muss {{limit}} Nummern enthalten'
}
}
})
};
this.getValidatorData = this.getValidatorData.bind(this);
this.getClasses = this.getClasses.bind(this);
this.renderHelpText = this.renderHelpText.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
getValidatorData() {
return {
PLZ: this.props.zip
};
}
getClasses(field) {
return classnames({
'form-control': true,
'has-error': !this.props.isValid(field)
});
}
renderHelpText(message) {
return (
<span className='help-block'>{message}</span>
);
}
handleSubmit(evt) {
evt.preventDefault();
const onValidate = (error) => {
if (error) {
//form has errors; do not submit
} else {
this.props.onSubmit();
}
};
this.props.validate(onValidate);
}
render() {
return (
<form role="form" onSubmit={this.handleSubmit}>
<div className='row'>
<div className="col-md-10 col-md-offset-1">
<div className='form-group'>
<label htmlFor="zip">
Einsatzort
</label>
<br />
<input className={this.getClasses('PLZ')} id="PLZ" placeholder="Meine PLZ" type="text" onChange={this.props.onZipChange} onBlur={this.props.handleValidation('PLZ')} value={this.props.zip} />
{this.renderHelpText(this.props.getValidationMessages('PLZ'))}
</div>
</div>
</div>
<div className='row'>
<div className="col-md-10 col-md-offset-1">
<button className="btn btn-green btn-block">Next</button>
</div>
</div>
</div>
</form>
);
}
}
FormStep7.propTypes = {
errors: PropTypes.object,
validate: PropTypes.func,
isValid: PropTypes.func,
handleValidation: PropTypes.func,
getValidationMessages: PropTypes.func,
clearValidations: PropTypes.func
};
export default validation(strategy)(FormStep7);
What am I doing wrong?
I found out that the issue was on Joi.number(). I changed the validation to match a Regex String pattern and then it worked.
this.validatorTypes = {
PLZ: Joi.string().regex(/^[0-9]{5}$/).label('PLZ').options({
language: {
string: {
regex: {
base: "mit 5 Nummern wird benötigt"
}
}
}
})
};