React state vs props for complex structure - api

I did example where my react fetches data from API.
I have the following mockup
List of objects
-- Object
--- Select field
this is my OrdersList.jsx
import React from 'react';
import Order from './Order';
class OrdersList extends React.Component {
constructor(props) {
super(props)
this.state = { data: [] }
}
componentDidMount() {
$.ajax({
url: '/api/v1/orders',
success: data => this.setState({ data: data }),
error: error => console.log(error)
})
}
render() {
return (
<div className="row">
<div className="col s12">
<table className="floatThead bordered highlight">
<thead>
<tr>
<th>id</th>
<th>status</th>
</tr>
</thead>
<Order data = { this.state.data } />
</table>
</div>
</div>
)
}
}
export default OrdersList;
here is my Order.jsx (it has Item to list and ItemStatus)
import React from 'react'
class OrderStatus extends React.Component {
constructor(props) {
super(props)
this.state = { data: [] }
}
handleChange(event) {
let data = {
order: {
status: event.target.value
}
}
console.log(event)
$.ajax({
method: 'PUT',
url: `/api/v1/orders/${event.target.id}`,
data: data,
success: data => (console.log(data), this.setState({ data: data })),
error: error => console.log(error)
})
}
render() {
return (
<div className="row">
<div className="input-field">
<p>status: {this.props.data.status}</p>
<select value={ this.props.data.status } id={ this.props.data.id } onChange={ this.handleChange } className="browser-default" >
<option value='neworder'>new</option>
<option value='pendingorder'>pending</option>
<option value='sentorder'>sent</option>
<option value='completedorder'>done</option>
</select>
</div>
</div>
)
}
}
class Order extends React.Component {
constructor(props) {
super(props)
this.state = { data: [] }
}
render() {
return (
<tbody>
{
this.props.data.map(
order =>
<tr key={order.id}>
<td>
# {order.id}
</td>
<td>
<OrderStatus data={ order } />
</td>
</tr>
)
}
</tbody>
)
}
}
export default Order;
What I am not able to understand is how to update my item status on ajax callback (there is update in backend, it works just fine, but how to update my status state on child item which is being listed by map function).. Appreciate for your input!

Your code looks fine, you are just missing a bind statement.
Add the following to your constructor and it should work:
this.handleChange = this.handleChange.bind(this);
For reference check this link.

Related

Error in Vuex Computed property was assigned to but it has no setter

I am trying to display a list of Invites to groups.
Sometimes this component displays as expected, and sometimes not at all. This error returns:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Computed property "groupInvites" was assigned to but it has no setter.
found in
---> <InviteList> at src/components/Invites/InviteList.vue
<UserProfile> at src/views/UserProfile.vue
<App> at src/components/App.vue
<Root>
Here is the it component generating the error:
<template>
<div class="">
<h4 mt-10 class="text-warning">Your Current Invites:</h4>
<table class="table">
<thead>
<tr>
<th>Group Name</th>
<th>Invited By</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="(invite, i) in groupInvites" :key="`${i}-${invite.id} `">
<td>
<router-link :to="`/groups/${invite.group_id}`" class="lightbox">
{{ invite.group_name }}
</router-link>
</td>
<td>{{ invite.sender_id }}</td>
<td>{{ moment(invite.created_at).strftime("%A, %d %b %Y %l:%M %p") }}</td>
<td scope="row">
<a class="btn btn-success mr-3" #click="acceptInvite(invite)">
Join Group
</a>
<a flat color="grey" #click="deleteInvite(invite.id)">
<i class="fa fa-trash " ></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import moment from 'moment-strftime';
import InvitesService from '../../services/InvitesService';
import UsersService from '../../services/UsersService';
export default {
name: "InviteList",
components: {
// NewInvite
},
props: {
// user: {
// type: Object,
// required: true
// },
},
computed: {
user() {
return this.$store.state.auth.user
},
groupInvites() {
return this.$store.state.invites.groupInvites;
}
},
mounted() {
this.getInvites();
},
methods: {
async getInvites () {
console.log('in invitelist, getting invites for user: ', this.user.id)
this.groupInvites = await this.$store.dispatch("getGroupInvites", this.user);
},
async getUser (id) {
this.sender = await UsersService.getUserById({
id: id
});
},
deleteInvite: async function (id) {
if(confirm("Do you really want to reject this invite?")) {
let response = await InvitesService.deleteInvite(id)
if (response.status === 200) {
console.log('In Invite.vue, invite deleted, about to emit');
this.$emit('invite-deleted');
}
}
this.getInvites();
},
async acceptInvite(invite) {
let result = await InvitesService.acceptInvite(invite.invitation_code)
this.$emit("membership-created");
console.log('this is the membership created: ', result.data)
// window.analytics.track('Group Joined', {
// title: this.group.name,
// user: this.$store.getters.user.username
// });
this.getInvites();
},
moment: function (datetime) {
return moment(datetime);
}
}
};
</script>
Separately, here is the store module:
import InvitesService from '#/services/InvitesService'
export const state = {
groupInvites: []
}
export const mutations = {
setGroupInvites(state, groupInvites) {
state.groupInvites = groupInvites;
}
}
export const actions = {
getGroupInvites({ commit }, user) {
console.log('in store. getting invites, for user: ', user.id)
InvitesService.getAllUserInvitation(user.id)
.then(resp => {
console.log('in store, getGroupInvites,this is groupInvites: ', resp.data);
commit('setGroupInvites', resp.data);
});
}
}
export const getters = {
}
Incidentally, getGroupInvites is being called twice. here are the console.logs:
in invitelist, getting invites for user: 9
invites.js?d00b:16 in store. getting invites, for user: 9
InvitesService.js?109c:10 in service getting all user invites for user: 9
invites.js?d00b:16 in store. getting invites, for user: undefined
InvitesService.js?109c:10 in service getting all user invites for user: undefined
notice user is undefined on the second go around.
It is possible to assign a value to a computed if you've defined it using a computed setter, but you haven't and probably don't need to. So this line is wrong and throws the error because it tries to do that:
this.groupInvites = await this.$store.dispatch("getGroupInvites", this.user);
But it's ok because this.groupInvites already gets its value reactively from the same state that getGroupInvites action populates anyway (state.groupInvites) so it's also unnecessary. Change that line to:
this.$store.dispatch("getGroupInvites", this.user);
and allow the computed to update itself.

UNABLE TO FILTER FIREBASE LIST USING SEARCH BAR

I am able to retrieve a list of users from firebase realtime database. However, I am stuck on how to apply a filter to my retrieved list, such that I can only see users who match what I’ve typed on the search bar.
I have followed the example on this site Filtering Observable with Rxjs, which is similar to what I'm trying to achieve.
model.ts
export interface AppUser {
userId: string;
firstname: string;
lastname: string;
phone: string;
email: string;
isAdmin: boolean;
photoURL: string;
}
User.service.ts
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireObject } from '#angular/fire/database';
#Injectable({
providedIn: 'root'
})
export class AdminUserService {
userRef: AngularFireObject<any>;
constructor(private db: AngularFireDatabase) { }
getAll() {
return this.db.list('/users').snapshotChanges();
}
get(id: string) {
this.userRef = this.db.object('users/' + id);
return this.userRef;
}
User.component.ts
import { Component, OnInit } from '#angular/core';
import { AdminUserService } from 'src/app/services/admin-user.service';
import { map } from 'rxjs/operators';
import { AppUser } from 'src/app/models/app-user';
#Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.scss']
})
export class UsersComponent implements OnInit {
appUser: AppUser[];
filteredUser: any[];
constructor(private adminUserService: AdminUserService) {
this.adminUserService.getAll()
.pipe(map((users: any[]) => users.map(user =>
({ id:user.key,...user.payload.val() })
).filter(curUser => curUser.firstname === this.filteredUser)
)
);
}
filter(query: string) {
this.filteredUser = (query) ? this.appUser.filter(u =>
u.firstname.toLowerCase().includes(query.toLowerCase())) :
this.appUser;
}
user.component.html
<p>
<a routerLink="/admin/users/new" class="btn btn-primary">New User</a>
</p>
<p>
<input #query (keyup)="filter(query.value)" type="text" class="fom-
control" placeholder="Search...">
</p>
<tbody>
<tr *ngFor="let u of filteredUser">
<td>{{ u.firstname }} {{ u.lastname }}</td>
<td>{{ u.email }}</td>
<td>{{ u.phone }}</td>
<td>{{ u.isAdmin }}</td>
<td>
<a [routerLink]="['/admin/users/', u.id]">Edit</a>
</td>
</tr>
</tbody>
Up to this point I am not getting errors, but when i add filter method in component.ts file I get no list from firebase.
export class UsersComponent implements OnInit, OnDestroy {
appUser: AppUser[];
filteredUser: AppUser[];
subscription: Subscription;
constructor(private adminUserService: AdminUserService) { }
ngOnInit() {
this.subscription = this.adminUserService.getAll()
.subscribe(a => this.filteredUser = this.appUser = a);
}
filter(query: string) {
this.filteredUser = (query) ?
this.appUser.filter(u => u.firstname.toLowerCase().includes(query.toLowerCase())) :
this.appUser;
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
getAll(): Observable<any[]> {
return this.db.list('/users').snapshotChanges()
.pipe(
map(actions =>
actions.map(a => ({ key: a.key, ...a.payload.val() }))
)
);
}
<tr *ngFor="let u of filteredUser">
<td>{{ u.firstname }} {{ u.lastname }}</td>
<td>
<a [routerLink]="['/admin/users/', u.id]">Edit</a>
</td>
</tr>

Vuetify Using datatable with external data from an API with Vuex

I want to use the vuetify framework with Vuex , but there is limited documentation about using it with Vuex.
I want to:
Get data from an external API ( but only the data needed )
Then Save the data in state and edit or whatever
Then push any changes back to the api
I have tried some of the external pagination and sorting examples with vuetify , but I can't get it to show all record count unless I hard code it.
I am quite new to Vue and Vuetify , so maybe I am misunderstanding something.
<template>
<div>
<v-data-table
:headers='headers'
:items='items'
:length='pages'
:search='search'
:pagination.sync='pagination'
:total-items='totalItemCount'
class='elevation-1'
>
<template slot='items' slot-scope='props'>
<td class='text-xs-right'>{{ props.item.id }}</td>
<td class='text-xs-right'>{{ props.item.first_name }}</td>
<td class='text-xs-right'>{{ props.item.last_name }}</td>
<td class='text-xs-right'>{{ props.item.avatar }}</td>
</template>
</v-data-table>
</div>
</template>
<script>
import moment from 'moment'
import axios from 'axios'
export default {
name: 'test-table',
watch: {
pagination: {
async handler () {
const rowsPerPage = this.pagination.rowsPerPage
// const skip = (this.pagination.page - 1) * rowsPerPage
const pageNumber = this.pagination.page
const res = await axios.get(`https://reqres.in/api/users?page=${pageNumber}&per_page=${rowsPerPage}`)
this.items = res.data.data
this.$store.commit('saveTableData', this.items)
},
deep: true
}
},
computed: {
pages () {
return 171
},
totalItemCount () {
return 400
}
},
async mounted () {
const rowsPerPage = this.pagination.rowsPerPage
const skip = (this.pagination.page - 1) * rowsPerPage
const res = await axios.get(`https://reqres.in/api/users?page=${skip}&per_page=${rowsPerPage}`)
this.items = res.data.data
this.$store.commit('saveTableData', this.items)
},
methods: {
nzDate: function (dt) {
return moment(dt).format('DD/MM/YYYY')
}
},
data: () => ({
search: '',
// totalItems: 0,
items: [],
pagination: {
sortBy: 'Date'
},
headers: [
{ text: 'ID', value: 'id' },
{ text: 'First Name', value: 'first_name' },
{ text: 'Last Name', value: 'last_name' },
{ text: 'Avatar', value: 'avatar' }
]
})
}
This is my working setup:
<template>
<v-data-table
:total-items="pagination.totalItems"
:pagination.sync="pagination"
:items="rows"
:headers="columns">
<template slot="headers" slot-scope="props">
<tr :active="props.selected">
<th v-for="column in props.headers">
{{ column.value }}
</th>
</tr>
</template>
<template slot="items" slot-scope="props">
<tr>
<td v-for="cell in props.item.row">
<v-edit-dialog lazy>
{{ cell.value }}
<v-text-field
:value="cell.value"
single-line
counter>
</v-text-field>
</v-edit-dialog>
</td>
</tr>
</template>
</v-data-table>
</template>
<script>
export default {
data: () => ({
pagination: {
page: 1,
rowsPerPage: 10,
totalItems: 0
},
selected: []
}),
computed: {
columns: {
get () {
return this.$store.state.columns
}
},
rows: {
get () {
return this.$store.state.rows
}
}
},
methods: {
async getRowsHandler () {
try {
const {total} = await this.$store.dispatch('getRows', {
tableIdentifier: this.$route.params.tableIdentifier,
page: this.pagination.page,
size: this.pagination.rowsPerPage
})
this.pagination.totalItems = total
} catch (error) {
// Error
}
}
}
}
</script>
I didn't implement everything. If you miss a specific part ask again and I will update my example. One more tip: You should avoid watch deep wherever possible. It can result in heavy calculations.
Assuming this is Vuetify v1.5, the documentation on the total-items prop on data-tables states:
Caution: Binding this to a blank string or using in conjunction with
search will yield unexpected behaviours
If you remove the 'search' prop from your table the record count will show again. If you're doing external stuff anyway, you'll won't want the default search functionality.

Vue component not updated when props are async

I have this component
<template>
<div class="list-group">
<div v-for="user in data">
<!-- omitted for brevity -->
</div>
</div>
</template>
<script>
export default ("users-list", {
props: ['users']
,
data() {
return {
data: this.users
}
}
});
</script>
This component is used from another component witch get data with $.ajax (Promise) and set the data
<template>
<div>
<users-list v-bind:users="users"></users-list>
<div>
</template>
<script>
import UsersService from './users.service.js';
import UsersList from './users.list.vue';
export default ('users-main', {
components: {
'users-list': UsersList
},
mounted() {
this.refresh();
},
data() {
return {
data: null,
message: null,
user: null
}
},
methods: {
refresh() {
let service = new UsersService();
service.getAll()
.then((data) => {
this.data = data;
})
.catch((error) => {
this.message = error;
})
},
selected(user) {
this.user = user;
}
}
});
</script>
And this is the UsersService
import $ from 'jquery';
export default class UsersService {
getAll() {
var url = "/Api/Users/2017";
return new Promise((resolve, reject) => {
$.ajax({
url: url,
success(data) {
resolve(data);
},
error(jq, status, error){
reject(error);
}
});
});
}
}
As you can see the service get data with Promise, if I change
<div v-for="user in data">
into the property, I can see the users
<div v-for="user in users">
Question: How I can pass async props value to the components ?
You set data once "onInit" in users-list.
For reactivity you can do
computed:{
data(){ return this.users;}
}
or
watch: {
users(){
//do something
}
}
What do you mean by:
As you can see the service get data with Promise, if I change
<div v-for="user in data">
into the property, I can see the users
<div v-for="user in users">
Use <div v-for="user in users"> should work, so what's the problem?

Rendering array pictures from calling API in react.js

I have API which consists
"pictures": [
"http:\/\/storage\/web\/source\/images\/2016-10-28\/edac054f88fd16aee7bc144545fea4b2.jpg",
"http:\/\/storage\/web\/source\/images\/2016-10-28\/9aa3217f37f714678d758de6f7f5222d.jpg",
"http:\/\/storage\/web\/source\/images\/2016-10-28\/5164ed92c205dc73a37d77e43fe1a284.jpg"
]
I have to render these pictures in Carousel. The problem is I have no idea how to render that pictures from an array, means that each picture should be outputted in each slider separately.
That is my code:
const API = 'http://...';
export default class Api extends React.Component {
constructor(props) {
super(props)
this.state = {
slider_pics:[
],
}
}
fetchProfile(id) {
let url = `${API}${name}`;
fetch(url)
.then((res) => res.json() )
.then((data) => {
this.setState({
slider_pics:data.data.pictures,
})
})
.catch((error) => console.log(error) )
}
componentDidMount() {
this.fetchProfile(this.state.name);
}
render() {
return (
<div>
<div>
<Carousel data={this.state}/>
</div>
</div>
)
}
}
export default class Carousel extends React.Component {
render() {
let data = this.props.data;
return(
<div>
<React_Boostrap_Carousel animation={true} className="carousel-fade">
<div >
<img style={{height:500,width:"100%"}} src={data.slider_pics} />
</div>
<div style={{height:500,width:"100%",backgroundColor:"aqua"}}>
456
</div>
<div style={{height:500,width:"100%",backgroundColor:"lightpink"}}>
789
</div>
</React_Boostrap_Carousel>
</div>
)
}
};
In this code all the URL images are rendering in one slide, I need each picture renders separately in each slide. Please help.
I almost figured out on my own. In Carousel component we have to set the loop in constructor and return that loop in map. Shortly, this is my code that is working for 100%
export default class Carousel extends React.Component {
constructor(props) {
super(props);
const slider_pics=[];
for (let i = 0; i < 10; i++) {
slider_pics.push(<React_Boostrap_Carousel />);
}
this.state = {slider_pics};
}
render() {
let data = this.props.data;
return(
<div>
<React_Boostrap_Carousel animation={true} className="carousel-fade">
{data.slider_pics.map((slider_pic, index) => (
<div key={index}>
<img style={{heght:200, width:1000}} src={slider_pic} />
</div>
))}
</React_Boostrap_Carousel>
</div>
)
}
};
The API component will be the same, just need to update the Carousel component like code above