How to add "handleRemove" function to Graphql both Query and Delete Mutation? - mongodb

My Application is using React, Apollo client 2, Apollo Server and MongoDB as the database.
I try to add "handleRemove" function to query page that I use React.
But when I click "Delete" button, I get "deleteCustomer" is not the function.
Here is my code of this page, please help to find out the solution.
import React, { Component } from 'react';
import gql from 'graphql-tag';
import { graphql, compose } from 'react-apollo';
import { Link } from 'react-router-dom';
import { Button, Alert } from 'react-bootstrap';
import Loading from '../components/Loading';
class Customers extends Component {
handleRemove = async (customerID) => {
const result = await this.props.deleteCustomer({
variables: {
_id: customerID,
},
})
}
render() {
const { data: {loading, CustomersList }, match, history } = this.props;
return ( !loading ? (
<div className="Customers">
<h2><p className="text-center">Customer List</p></h2>
<Button><Link to={`/customers/new`}>Add Customer</Link></Button>
<br />
<br />
{CustomersList.length ?
<table className="zui-table zui-table-rounded">
<thead>
<tr>
<th><p className="text-center">List</p></th>
<th>
</th>
<th><p className="text-center">Salution</p></th>
<th><p className="text-center">Firstname</p></th>
<th><p className="text-center">Lastname</p></th>
<th><p className="text-center">Nickname</p></th>
<th><p className="text-center">Email</p></th>
<th><p className="text-center">Phone</p></th>
<th><p className="text-center">Mobile</p></th>
<th />
<th />
</tr>
</thead>
<tbody>
{CustomersList.map(({ _id, salution, firstname, lastname, nickname, email, phone, mobile, total_spent, createdAt, updatedAt }, index) => (
<tr key={_id}>
<td>{index+1}</td>
<td>
</td>
<td>{salution}</td>
<td>{firstname}</td>
<td>{lastname}</td>
<td>{nickname}</td>
<td>{email}</td>
<td>{phone}</td>
<td>{mobile}</td>
<td>
<p className="text-center">
<button className="btn btn-info btn-sm"
onClick={() => history.push(`${match.url}/${_id}/edit`)}
>Edit</button>
</p>
</td>
<td>
<p className="text-center">
<button className="btn btn-danger btn-sm"
onClick={() => this.handleRemove(_id)}
>Delete</button>
</p>
</td>
</tr>
))}
</tbody>
</table> : <Alert bsStyle="warning">Nobody registered yet!</Alert>}
</div>
) : <Loading /> );
}
}
export const customersListQuery = gql`
query customersListQuery {
CustomersList {
_id
salution
firstname
lastname
nickname
email
phone
mobile
total_spent
createdAt
updatedAt
}
}
`;
export const deleteCustomer = gql`
mutation deleteCustomer($_id: ID!) {
deleteCustomer(_id: $_id) {
_id
}
}
`;
export default compose(
graphql(customersListQuery),
graphql(deleteCustomer, { name: deleteCustomer }),
)(Customers);

The name of the mutation should be a string. The quotes are missing:
...
export default compose(
graphql(customersListQuery),
graphql(deleteCustomer, { name: "deleteCustomer" }),
)(Customers);

Related

How can I use v-for tag to show elements in the data?

I am creating ec mock-up, and how can I use v-for tag
developing environment
Mac, Vue.js, Atom, Server hostname is https://euas.person.ee/
Rigth Now↓
What I want to do is showing Order ID and Option Image for each row like
Order ID | OrderDescription | Action
1 "OptionImage" Detail Button
OrderListingVue
<template>
<div class="OrderListing">
<h2>My Orders</h2>
<table class="table">
<tr>
<th>OrderId</th>
<th>OrderDescription</th>
<th>Action</th>
</tr>
<tr v-for="(cart, order) in this.orders" :key="order.id">
<td>{{order}}</td>
<td>{{cart}}</td>
<td>
<b-button variant="dark" :to=" '/orders/' + order">Detail</b-button>
</td>
</tr>
</table>
</div>
</template>
<script>
import axios from "axios";
export default {
name: 'OrderListing',
props: {
order: Object
},
data: function() {
return {
orders: []
}
},
mounted() {
axios.get("https://euas.person.ee/user/orders")
.then(response => {
this.orders = response.data;
});
}
}
</script>
<style scoped>
.option-image {
max-height: 50px;
max-width: 100px;
}
</style>
Addition
ShoppingCartVue↓
<template>
<div class="shopping-cart-page">
<h2>ShoppingCart</h2>
<table class="table">
<tr>
<th>Product</th>
<th>Price</th>
<th>qty</th>
<th>Amount</th>
<th>Actions</th>
</tr>
<tr v-for="(item, index) in this.items" :key="item.productId + '_' + index">
<td>
<img :src="item.optionImage" class="option-image" />
</td>
<td>{{ item.price }}</td>
<td>{{ item.qty }}</td>
<td>{{ item.total }}</td>
<td>
<b-button variant="danger" #click="removeItem(index)">Remove</b-button>
</td>
</tr>
<tr class="total-row">
<td>TOTAL:</td>
<td></td>
<td></td>
<td>{{ total }}</td>
<td></td>
</tr>
</table>
<b-button variant="success" size="lg" #click="orderNow" v-if="this.items.length">Order Now!</b-button>
</div>
</template>
<script>
import axios from "axios";
export default {
name: 'ShoppingCartPage',
computed: {
items: function() {
return this.$root.$data.cart.items || [];
},
total: function() {
let sum = 0
for (const item of this.items) {
sum += item.total
}
return sum
}
},
methods: {
removeItem: function(index) {
if (!this.$root.$data.cart.items) this.$root.$data.cart.items = []
this.$root.$data.cart.items.splice(index, 1);
console.log(this.$root.$data.cart.items);
this.$root.$data.saveCart();
},
orderNow: function() {
let data = this.$root.$data
axios.post("https://euas.person.ee/user/carts/" + this.$root.$data.cart.id + "/orders/",
this.$root.$data.cart).then(function() {
data.reinitCart();
})
}
}
}
</script>
<style scoped>
.option-image {
max-height: 50px;
max-width: 100px;
}
</style>
From what I understood you are looking for something like this:
<div v-for="order in orders" :key="order.id">
<div v-for="item in order.items" :key="item.productId">
<img :src="item.optionImage" class="option-image" />
</div>
</div>

Error:error Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays

I am trying to bind some JSON data from my API to HTML and iterate it over it using *ngFor in angular 4. Unfortunately, I'm getting the error in this question's title.
The code in question:
countryList.ts
export interface CountryList {
UserName : string,
Country : string[],
}
Service.ts
export class LoginServicesService {
constructor(private o:Http) { }
functionCountriesList(userName:string)
{
return this.o.get('http://localhost:55760/api/Login/GetCountries/'+userName).map((res:Response) =><CountryList[]>res.json());
}
}
Component.ts
export class LoginComponent implements OnInit {
constructor(private loginService: LoginServicesService) { }
ngOnInit() {
}
countries: CountryList[];
countryValue: number;
countryName: string;
GenerateCountries(userName: string) {
this.loginService.functionCountriesList(userName).subscribe((data) => this.countries = data);
console.log(this.countries);
}
}
Component.HTML
<table align="center">
<tr>
<td><label id="lblUserName">UserName:</label></td>
<td><input #userName type="Textbox" id="txtUserName" (keyup.enter)="GenerateCountries(userName.value)" (blur)="GenerateCountries(userName.value)" /></td>
</tr>
<tr>
<td>
<label id="lblPassword">Password:</label>
</td>
<td><input type="password" id="txtPassword"/></td>
</tr>
<tr>
<td>Domain:</td>
<td>
<select id= "district" style="width:100%" (change)="selectdrop($event)">
<option value="NIL">Select Domain</option>
<option *ngFor="let c of countries;let i=index">{{c}}</option>
</select>
</td>
</tr>
</table>
JSON from api is
{
"UserName": "hello",
"Country": [
"South Africa",
"Russia",
"India",
"America",
]
}
Your problem is that you're trying to directly iterate through the result of your API call, which a JSON object as you've pasted. You need to sub-select the "Country" field from your API response first and iterate through that.

Angular2 Reactive Form Table

I am having some trouble looking up and attempting to do what I want to.
I have a table with inputs in each row, and I want each row which is created using an ngFor, to be considered a form group.
Within each form group I want a validation in that if any of the controls are filled within the row, that the entire form group needs to be filled before submission can be done.
Here is what I have so far in my template.
I don't have anything in the component yet as Angular.io and searching around for a few hours has not shown me anything close to what I want.
<form>
<table id="table" class="mdl-data-table mdl-js-data-table mdl-data-table mdl-shadow--2dp">
<thead>
<tr>
<th>Day</th>
<th>Description</th>
<th>Open Time</th>
<th>Close Time</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let scheduleDetail of scheduleDetails">
<td style="padding: 0 8px 0 8px;">{{weekdayConverter(scheduleDetail.dayId)}}</td>
<td class="pad-input">
<mdl-textfield style="max-width:100px;" type="text" class="font" name="description" [(ngModel)]="scheduleDetail.description"></mdl-textfield>
</td>
<td>
<mdl-textfield style="max-width:75px" type="text" error-msg="hh:mm" name="openTime" pattern="([01]\d|2[0-3]):?([0-5]\d)" [(ngModel)]="scheduleDetail.openTime"></mdl-textfield>
</td>
<td>
<mdl-textfield style="max-width:75px" type="text" error-msg="hh:mm" name="closeTime" pattern="([01]\d|2[0-3]):?([0-5]\d)" [(ngModel)]="scheduleDetail.closeTime"></mdl-textfield>
</td>
</tr>
</tbody>
</table>
</form>
Update
Added the following to the template:
Changed inputs to:
<mdl-textfield (keyup)="formChange(scheduleDetail)" style="max-width:100px;" type="text" class="font" name="description" [(ngModel)]="scheduleDetail.description"></mdl-textfield>
Added the following to the component:
formChange(detail:scheduleDetail){
if(this.checkValid(detail)==false)
this.scheduleDetails.filter(detail => detail == detail)[0].require=true;
else
this.scheduleDetails.filter(detail => detail == detail)[0].require=false;
this.checkForm();
}
checkValid(detail:scheduleDetail){
if(detail.description!=null && detail.description!=""){
if(this.checkTime(detail))
return true
else
return false
}
else
return true
}
checkTime(detail:scheduleDetail){
if(
(detail.openTime!=null && detail.closeTime!=null) &&
( detail.openTime!="" && detail.closeTime!="") &&
(this.checkRegExp(detail.openTime) && this.checkRegExp(detail.closeTime))
){
return true
}
else if((this.checkRegExp(detail.openTime) && this.checkRegExp(detail.closeTime))){
return true
}
else return false
}
checkRegExp(time:string){
let timeRegExp = /([01]\d|2[0-3]):?([0-5]\d)/;
if(timeRegExp.test(time)){
return true;
}
else
return false;
}
checkForm(){
let valid: boolean = true;
this.scheduleDetails.forEach(detail => {
if(detail.require==true){
valid = false;
}
});
this.scheduleDetails.forEach(detail => {
if(detail.description=="" || detail.description==null){
valid = false;
}
});
this.formValid = valid;
}
Model Driven Forms
You are using a template driven form, which is hard to scale, and maintain.
Here, i will guide you to migrate to a model driven form.
Component
export class WeekScheduleComponent {
// Our empty Form
myForm: FormGroup;
constructor(private fb: FormBuilder){
// We inject FormBuilder to our component
// Now, we instantiate myForm with FormBuilder
// Notice that myForm is a FormGroup which contains an empty FormArray
this.myForm = this.fb.group({
scheduleDetail: this.fb.array([])
})
}
addRow(){
// This function instantiates a FormGroup for each day
// and pushes it to our FormArray
// We get our FormArray
const control = <FormArray>this.myForm.controls['scheduleDetail'];
// instantiate a new day FormGroup;
newDayGroup: FormGroup = this.initItems();
// Add it to our formArray
control.push(newDayGroup);
}
initItems(): FormGroup{
// Here, we make the form for each day
return this.fb.group({
description: [null, Validators.required],
openTime: [null, Validators.required],
closeTime: [null, Validators.required]
});
}
submit(){
// do stuff and submit result
console.log(this.myForm.value);
}
}
in your template:
<form [formGroup]="myForm" *ngIf="myForm">
<table formArrayName="scheduleDetail">
<tr *ngFor="let item of myForm.controls.scheduleDetail.controls; let i=index"
[formGroupName]="i" >
<td><input type='text' formControlName="description"></td>
<td><input type='text' formControlName="openTime"></td>
<td><input type='text' formControlName="closeTime"></td>
</tr>
</table>
</form>
<button (click)="addRow()">Add new item</button>
<button (click)="submit()" [disabled]="!myForm.valid">Submit</button>
Component
import { Component, OnInit } from '#angular/core';
import {FormGroup, FormBuilder, FormControl, Validators, FormArray} from '#angular/forms';
#Component({
selector: 'app-sales',
templateUrl: './sales.component.html',
styleUrls: ['./sales.component.css']
})
export class SalesComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder){
}
// getter to get a reference to scheduleDetail form array in myForm form group
get scheduleDetail():FormArray{
return <FormArray>this.myForm.get('scheduleDetail')
}
// add a new row, get reference to form array using getter method and push form group into it
addRow(){
this.scheduleDetail.push(this.initItems());
}
// make a form for each row!
initItems(): FormGroup{
return this.fb.group({
description: [null, Validators.required],
openTime: [null, Validators.required],
closeTime: [null, Validators.required]
});
}
submit(){
console.log(this.myForm.value)
}
ngOnInit() {
this.myForm = this.fb.group({
scheduleDetail: this.fb.array([this.initItems()])
})
}
}
Template
<form [formGroup]="myForm" *ngIf="myForm">
<table formArrayName="scheduleDetail">
<thead>
<tr>
<th >Desc</th>
<th >Open Time</th>
<th >Close Time</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of myForm.controls.scheduleDetail.controls; let i=index"
[formGroupName]="i" >
<td><input type='text' formControlName="description"></td>
<td><input type='text' formControlName="openTime"></td>
<td><input type='text' formControlName="closeTime"></td>
</tr>
</tbody>
</table>
</form>
<button (click)="addRow()">Add new item</button>
<button (click)="submit()" [disabled]="!myForm.valid">Submit</button>

How can I re-render the page after post request with react/redux?

There is a short time between the posting and the response from the server. How is it possible to cause your component to re-render when you get your positive response? I tried componentWillGetProps(){} and if-statements like
if(this.props.incomingItems){return: this.props.incomingItems}
but it none of them worked out. How did you solve this problem?
PS I'm using redux and axios for the requests.
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
import * as actions from '../../actions';
class eventView extends Component {
componentWillMount() {
this.props.eventView(this.props.params.eventID);
}
createNewRole(roleName){
this.props.createNewRole(roleName, this.props.params.eventID);
};
renderUsers(){
return this.props.eventDetails.userList.map((user)=>{
return(
<li className='list-group-item eventUserList' background-color="#f2f2f2" key={user._id}>
{user.userName}
</li>
);
});
};
deleteListItem(key){
const newKey = key.dispatchMarker.substr(44, 24);
this.props.RemoveRoleFromList(newKey)
this.props.fetchEvents();
}
renderRoles(){
return this.props.eventDetails.role.map((role)=>{
return(
<li className='list-group-item roleList' key={role._id}>
{role.roleName}
<img className="deleteListItem"
src="/img/trash.png"
key={role._id}
onClick={this.deleteListItem.bind(this)}/>
</li>
);
});
};
render() {
const { handleSubmit, fields: {eventName,location, eventPassword, roleName} } = this.props;
if(this.props.roleList){
console.log(this.props.roleList)
}
if (this.props.eventDetails){
return (
<div className='container-fluid'>
<div className="roleBox">
<form onSubmit={handleSubmit(this.createNewRole.bind(this))}>
<div>
<input {...roleName}
className="form-control roleBoxInputBar"
autoComplete="off"/>
<button className="RoleButton">Save</button>
</div>
<div className="listOfRoles">
<ul className="listOfRoles pre-scrollable">
{this.renderRoles()}
</ul>
</div>
</form>
</div>
<div>
<div>
<h1 className="eventName">
{this.props.eventDetails.eventName}
</h1>
</div>
<br/>
<table>
<tbody>
<tr>
<td className="eventViewTableLocation">Location:</td>
<td className="eventViewTable">{this.props.eventDetails.location}</td>
</tr>
<tr>
<td className="eventViewTableLocation">Date:</td>
<td className="eventViewTable">12/Feb/2018</td>
</tr>
<tr>
<td className="eventViewTableLocation">Time Left:</td>
<td className="eventViewTable">2 Days 2 Hours</td>
</tr>
</tbody>
</table>
</div>
<div className='eventViewUserBox'>
<h4 className="listOfUsers">Organisers:</h4>
<ul>
{this.renderUsers()}
</ul>
</div>
</div>
);
}else {
return (
<div>
</div>
);
}
}
}
function mapStateToProps(state) {
return { eventDetails: state.api.eventDetails };
return { roleList: state.api.roleList };
return { createdRole: state.api.createdRole };
}
export default reduxForm({
form: 'eventView',
fields: ['eventName', 'location', 'eventPassword', 'roleName']
}, mapStateToProps, actions)(eventView);
And my axios post goes something like this
export function createNewRole({roleName}, eventID){
return function(dispatch) {
axios.post(`${ROOT_URL}createRole/`+eventID, {roleName})
.then(response => {
if (response.data){
dispatch({
type: CREATED_ROLE,
payload: response.data,
});
};
})
.catch(response => dispatch(authError(response.data.error)));
};
};
Reducer:
export default function(state = {}, action) {
switch(action.type) {
case FETCH_ROLES:
return { ...state, roleList: action.payload };
case CREATED_ROLE:
return { ...state, createdRole: action.payload };
}
return state;
}
Thanks a lot!
function mapStateToProps(state) {
return { eventDetails: state.api.eventDetails };
return { roleList: state.api.roleList };
return { createdRole: state.api.createdRole };
}
This function always returns the first object. It should be:
function mapStateToProps(state) {
return {
eventDetails: state.api.eventDetails,
roleList: state.api.roleList,
createdRole: state.api.createdRole
};
}
I'm guessing roleList and createdRole are always undefined? Also it would be good if you would show the reducer.

How do you isolate changes in React Props/State

Within the context of Meteor/React I have a table component that subscribes to a Mongo Database, this subscription has a limit parameter that can be updated via props. I'm not sure why but it appears that the componentDidMount Lifecycle function is firing almost continually leading to unpleasant flickering.
The code is quite lengthy but I've attached what I think is the problematic section - more generally, how do you go about isolating what change in state/props is leading to a re-render? I've tried monitoring Chrome Tools but see no change there.
Any feedback or help appreciated!
import React from 'react';
import booksSingleLine from '../booksTable/booksSingleLine';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class booksListingTable extends TrackerReact(React.Component) {
constructor(props) {
super(props);
console.log("Constructor props are" + this.props.LimitProp);
}
componentWillMount() {
console.log("Mounting booksListingTable");
console.log("Will mount props are" + this.props.LimitProp);
this.state = {
}
}
componentDidMount(props){
// console.log("Did mount and props are" +props.LimitProp);
var limit = this.props.LimitProp
limit = parseInt(limit) || 5;
this.state = {
subscription: {
booksData: Meteor.subscribe("allbooks",{sort: {_id:-1}, limit: limit})
}
}
}
componentWillReceiveProps(props) {
console.log("component will get new props " + props.LimitProp);
// Note that for this lifecycle function we have to reference the props not this.props
// console.log("component will get weird props " + this.props.LimitProp);
var limit = props.LimitProp
limit = parseInt(limit) || 5;
this.state = {
subscription: {
booksData: Meteor.subscribe("allbooks", {limit: limit})
}
}
}
componentWillUnmount() {
}
booksDataLoad(){
var filteredCity = this.props.FilterProp;
console.log("filter is " + filteredCity);
if (filteredCity) {
console.log("just passing a few things")
return (
remotebookss.find({location: filteredCity}).fetch()
)
}
else {
console.log("passing everything to table");
return(
remotebookss.find().fetch()
)}}
render() {
return(
<div>
<table className="ui celled table">
<thead>
<tr>
<th onClick ={this.props.HeaderOnClick}>Name</th>
<th>Date</th>
<th>Summary</th>
<th>Site address</th>
<th>Price is</th>
</tr></thead>
<tbody>
{this.booksDataLoad().map( (booksData)=> {
return <booksSingleLine key={booksData._id} booksData={booksData} />
})}
</tbody>
<tfoot>
<tr><th colspan="3">
<div class="ui right floated pagination menu">
<a class="icon item">
<i class="left chevron icon"></i>
</a>
<a class="item" onClick= {this.props.methodLoadMore}>Load More</a>
<a class="icon item">
<i class="right chevron icon"></i>
</a>
</div>
</th>
</tr></tfoot>
</table>
</div>
)
}
}
I would strongly recommend you read the following blog post --
https://www.discovermeteor.com/blog/data-loading-react/
Things I see:
You're resetting state all over the place
You're not managing the subscription as a clear object - your code leaks them (FYI).
Here's a version that uses props and default props to setup your Limit, with that it checks only on startup and change to change the subscription.
import React from 'react';
import booksSingleLine from '../booksTable/booksSingleLine';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class booksListingTable extends TrackerReact(React.Component) {
static propTypes = {
LimitProp: React.PropTypes.number,
}
static defaultProps = {
LimitProp: 5,
}
constructor() {
super();
console.log("Constructor props are" + this.props.LimitProp);
const subscription = Meteor.subscribe("allbooks",{sort: {_id:-1}, limit: this.props.LimitProp})
this.state = {
booksData: subscription,
}
}
componentWillReceiveProps(nextProps) {
console.log("component will get new props " + nextProps.LimitProp);
// Start new subscription - if it's changed
if (this.props.LimitProp != nextProps.limitProp) {
// Stop old subscription
this.state.booksData.stop()
// Setup new subscription
const subscription = Meteor.subscribe("allbooks",{sort: {_id:-1}, limit: nextProps.LimitProp})
this.setState({ booksData: subscription })
}
}
componentWillUnmount() {
// Stop old subscription
this.state.booksData.stop()
}
booksDataLoad(){
var filteredCity = this.props.FilterProp;
console.log("filter is " + filteredCity);
if (filteredCity) {
console.log("just passing a few things")
return (
remotebookss.find({location: filteredCity}).fetch()
)
}
else {
console.log("passing everything to table");
return(
remotebookss.find().fetch()
)
}
}
render() {
return(
<div>
<table className="ui celled table">
<thead>
<tr>
<th onClick ={this.props.HeaderOnClick}>Name</th>
<th>Date</th>
<th>Summary</th>
<th>Site address</th>
<th>Price is</th>
</tr></thead>
<tbody>
{this.booksDataLoad().map( (booksData)=> {
return <booksSingleLine key={booksData._id} booksData={booksData} />
})
}
</tbody>
<tfoot>
<tr><th colspan="3">
<div class="ui right floated pagination menu">
<a class="icon item">
<i class="left chevron icon"></i>
</a>
<a class="item" onClick= {this.props.methodLoadMore}>Load More</a>
<a class="icon item">
<i class="right chevron icon"></i>
</a>
</div>
</th>
</tr></tfoot>
</table>
</div>
)
}
}