Relay Moder - Pagination - relay

I am already working on Pagination.
I used PaginationContainer for that. It work’s but no way what I am looking for.
I got button next which call props.relay.loadMore(2) function. So when I click on this button it will call query and add me 2 more items to list. It works like load more. But I would like instead of add these two new items to list, replace the old item with new.
I try to use this getFragmentVariables for modifying variables for reading from the store but it’s not working.
Have somebody Idea or implemented something similar before?
class QueuesBookingsList extends Component {
props: Props;
handleLoadMore = () => {
const { hasMore, isLoading, loadMore } = this.props.relay;
console.log('hasMore', hasMore());
if (!hasMore() || isLoading()) {
return;
}
this.setState({ isLoading });
loadMore(1, () => {
this.setState({ isLoading: false });
});
};
getItems = () => {
const edges = idx(this.props, _ => _.data.queuesBookings.edges) || [];
return edges.map(edge => edge && edge.node);
};
getItemUrl = ({ bid }: { bid: number }) => getDetailUrlWithId(BOOKING, bid);
render() {
return (
<div>
<button onClick={this.handleLoadMore}>TEST</button>
<GenericList
displayValue={'bid'}
items={this.getItems()}
itemUrl={this.getItemUrl}
emptyText="No matching booking found"
/>
</div>
);
}
}
export default createPaginationContainer(
QueuesBookingsList,
{
data: graphql`
fragment QueuesBookingsList_data on RootQuery {
queuesBookings(first: $count, after: $after, queueId: $queueId)
#connection(
key: "QueuesBookingsList_queuesBookings"
filters: ["queueId"]
) {
edges {
cursor
node {
id
bid
url
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
`,
},
{
direction: 'forward',
query: graphql`
query QueuesBookingsListQuery(
$count: Int!
$after: String
$queueId: ID
) {
...QueuesBookingsList_data
}
`,
getConnectionFromProps(props) {
return props.data && props.data.queuesBookings;
},
getFragmentVariables(prevVars, totalCount) {
console.log({ prevVars });
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, variables, fragmentVariables) {
return {
count: variables.count,
after: variables.cursor,
queueId: fragmentVariables.queueId,
};
},
},
);

As I figure out, there are two solutions, use refechConnection method for Pagination Container or use Refech Container.

Related

how to make component not re-render when next page

i'm doing asynchronous processing while waiting for created to finish then start running mouted , everything is fine, but something is causing my component to re-render, looks like this: video
how do i handle the above problem
here is my code:
<template>
<div class="wrapper">
<div class="main-panel">
<dashboard-content #click.native="toggleSidebar" />
</div>
<Sidebar :sidebar-data="dataSidebar"/>
</div>
</template>
data() {
return {
dataSidebar: [],
role: adminRole.OWNER,
isPending: null, // Save promise handler
};
},
created() {
if (!(STORE_ADMIN_AUTH_KEY in this.$store._modules.root._children)) {
this.$store.registerModule(STORE_ADMIN_AUTH_KEY, store);
}
if (localStorage.getItem(ADMIN_AUTH_TOKEN_KEY)) {
const res = this.$store.dispatch(STORE_ADMIN_AUTH_KEY + "/getInfo");
this.isPending = new Promise((solver, reject) => {
res.then((data) => {
localStorage.setItem("AUTH",JSON.stringify(data.role ? data.role : adminRole.OWNER));
solver();
});
});
}
},
async mounted() {
await this.isPending;
this.getSitebarItems();
},
methods: {
getSitebarItems() {
if (localStorage.getItem("AUTH")) {
this.role = localStorage.getItem("AUTH");
}
if (this.role == adminRole.OWNER) {
this.dataSidebar = sidebarItems;
return;
}
sidebarItems.forEach((element) => {
if (element.onlyOwner == 0) {
this.dataSidebar.push(element);
}
});
},
},
thanks for your help!
Maybe you could try creating a copy of the items to prevent triggering reactivity.
getSitebarItems() {
let data = sidebarItems.slice();
if (this.role == adminRole.OWNER) {
this.dataSidebar = data;
return;
}
data = data.filter((element) => {
return element.onlyOwner == 0;
});
this.dataSidebar = data;
}

Vue2 set a variable from an api callback

I have this function return a call back as:
function fetchShifts(ctx, callback) {
const accountId = selectedAccount.value.value.id
store.dispatch('app-action-center/fetchShifts', {
accountId,
})
.then(shifts => {
const data = []
shifts.forEach(async (shift, index) => {
const user = await store.dispatch('app-action-center/fetchUserDetails',
{
assignedTo: shift.assignedTo,
})
.then(res => res)
data.push({
...shift,
user: user.fullName,
})
if (index === (shifts.length - 1)) { callback(data) }
})
})
}
In the vue file I try to set it as:
data() {
return {
shifts: this.fetchShifts,
}
},
or
data() {
return {
shifts: null,
}
},
created() {
this.shifts = this.fetchShifts()
}
None of them work, I want to make this shifts variable ready when the component mounted so I can put it in the <app v-for="shift in shifts" />
At this moment, this code work fine with <b-table :items="fetchShifts /> but I don't know how to convert to <ul v-for="shift in shifts></ul>
Try like this:
<ul v-for="shift in shifts" :key="shift.id">
</ul>
export default
{
data()
{
return {
shifts: [],
};
},
created()
{
this.fetchShifts(undefined, (shiftsArray) =>
{
this.shifts = shiftsArray;
});
}
}
Explanation - initially you start with an empty array. Then you asynchronously fetch the shifts. The callback is called as soon as all the shifts and the corresponding users have been fetched - and in this callback you update the array with the shifts, which in turn triggers component re-rendering.
Vue is truly amazing!

Server-side input validation with Quasar (Vue)

I'm looking for a reusable solution for server side input validation with Quasar Framework.
I use the q-input field. There are 2 props that can be used for the error output error and error-message. My server response on a validation error is a 400 response with this json
{
errors: { username: ['Username is to long.', 'User not valid'] },
title: 'One or more validation errors occurred.',
status: 400,
traceId: '80000005-0000-ff00-b63f-84710c7967bb'
}
I am currently using this logic, but I cannot move it to a mixin because I need access to the error field. The full example is available here codesandbox.io
<q-input
v-model="username"
filled
label="Username *"
:error-message="getErrorForField('username')"
:error="isErrorForField('username')"
/>
getErrorForField(field) {
if (!this.errors) {
return ''
}
const keys = Object.keys(this.errors)
const key = keys.find(
element => element.toLowerCase() === field.toLowerCase()
)
if (this.errors[key]) {
return this.errors[key].join('\r\n')
}
},
isErrorForField(field) {
if (!this.errors) {
return false
}
const keys = Object.keys(this.errors)
const key = keys.find(
element => element.toLowerCase() === field.toLowerCase()
)
if (this.errors[key]) {
return true
}
}
I have found a solution with the new vue3 composition-api. I now have a reusable logic.
MyComponent.vue
<template>
<q-input
v-model="username"
filled
label="Username *"
:error-message="getErrorForField('username')"
:error="isErrorForField('username')"
/>
</template>
<script>
import { validationHelper } from 'src/helper/validationHelper'
export default {
name: 'MyComponent',
setup () {
const { showValidationError, setValidationErrors, getValidationErrors, hasValidationErrors } = validationHelper()
return {
showValidationError,
setValidationErrors,
getValidationErrors,
hasValidationErrors
}
},
methods: {
async add () {
try {
//axios request
} catch (error) {
this.setValidationErrors(error.response.data.errors)
this.showValidationError()
}
}
}
}
</script>
validationHelper.js
import { ref } from '#vue/composition-api'
export function validationHelper () {
const errors = ref([])
function getValidationErrorMessages (field) {
if (!errors.value) {
return []
}
const keys = Object.keys(errors.value)
const key = keys.find(element => element.toLowerCase() === field.toLowerCase())
if (errors.value[key]) {
return errors.value[key]
}
return []
}
function getValidationErrors (field) {
const errors = getValidationErrorMessages(field)
if (errors.length !== 0) {
return errors.join('\r\n')
}
return ''
}
function hasValidationErrors (field) {
if (getValidationErrorMessages(field).length !== 0) {
return true
}
return false
}
function setValidationErrors (payload) {
errors.value = payload
}
function showValidationError () {
this.$q.notify({
type: 'negative',
message: 'Validation failure',
caption: 'please check the inputs'
})
}
return {
showValidationError,
setValidationErrors,
getValidationErrors,
hasValidationErrors
}
}

Update image src on the fly with VueJS

I'm a new VueJS user, currently struggling with updating image src on the fly. This is what I've got:
Template:
<div v-for="place in places">
<img
v-bind:src="isPlacePrivate(place.data.place_is_private)"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
>
</div>
<script>
export default {
data: function () {
return {
places: null,
}
},
mounted () {
this.username = this.$route.params.username;
axios.get('/api/' + this.username + '/places')
.then(response => {
this.places = response.data.data;
})
.catch(error => {
// show error
});
},
methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
var src = '/icons/padlock-color.png'
} else {
var src = '/icons/padlock.png'
}
return src;
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
let newValue = response.data.data.private;
this.isPlacePrivate(newValue);
})
.catch(error => {
// show error
});
},
},
}
</script>
On a page load -> if a particular place is private it will show colored padlock icon or uncolored padlock if a place is public!
A user will be able to press on the padlock icon and change the value from public->private or private->public.
Everything is working fine but the padlock image is not updating on the fly when a user is clicking on it, I need to refresh a page to see changes! How to make it work?
I would suggest using a computed property so that it is reactive
Also according to your updates you are looping through an array of places so when you get your response from your axios call instead of just updating the icon I would try replacing the object in the array so I created the method called updatePlace() and I pass in the response object.
And change your places in the v-for to a computed property as well so that it is also reactive
Template:
<div v-for="place in placesArray" :key="index" v-if="places">
<img
v-bind:src="imgSrc"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
v-if="imgSrc"
>
</div>
Script:
<script>
export default {
data() {
return {
src: '',
places: null
}
},
computed: {
imgSrc() {
return this.src
},
placesArray() {
return this.places
}
},
Methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
this.src = '/icons/padlock-color.png'
} else {
this.src = '/icons/padlock.png'
}
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
console.log(response);
let newValue = response.data.data;
this.updatePlace(newValue);
})
.catch(error => {
console.log(error);
});
},
},
updatePlace(newPlace) {
const index = this.places.findIndex(place => place.id === newPlace.id)
this.places.splice(index, 1, place)
},
created() {
this.username = this.$route.params.username;
axios.get('/api/' + this.username + '/places')
.then(response => {
this.places = response.data.data;
})
.catch(error => {
// show error
});
}
}
</script>
Also make sure to move your mounted method to a created() method so that it is called before anything else is trying to render.
Apparently the problem is that you are calling the function and printing its return on the <img v-bind:src>, the isPlacePrivate function returns a value, so when you use this function within the setPlaceAsPrivate it returns the value only in scope of setPlaceAsPrivate.
The isPlacePrivate function does not modify any data value of the component, so the image always remains the same. You just need to set a data and manipulate its value in the isPlacePrivate function.
Template
<img
v-bind:src="bindSrc"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
>
Script
<script>
export default {
data() {
return {
bindSrc: '/icons/padlock-color.png', // default img src value
... // your other values
}
},
Methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
this.bindSrc = '/icons/padlock-color.png'
} else {
this.bindSrc = '/icons/padlock.png'
}
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
console.log(response);
let newValue = response.data.data.private;
this.isPlacePrivate(newValue);
})
.catch(error => {
console.log(error);
});
},
}
}
</script>

Value not selected in dropdown, why?

I am using react native material ui component to select value and
set it. But selected value does not stay on the screen. Cannot see the
selected value. Why so?
`
onChangeHandler = (e) => {
this.setState({
selectValue: e
});
}
render() {
sports = [
{ value:'Basketball' },
{ value:'Football' },
{ value:'Tennis' },
{ value:'Volleyball' }
];
<Dropdown
value={this.state.selectValue}
data = {sports}
style={styles.Dropdown1}
onChangeText = {(event) => this.onChangeHandler(event)}
><Dropdown>
}
Try to console.log() the event you are passing, and see what is raised in that event
I would try something like the following:
onChangeHandler = (evt) => {
this.setState({
selectValue: evt.target.value
});
}
render() {
sports = [
{ value:'Basketball' },
{ value:'Football' },
{ value:'Tennis' },
{ value:'Volleyball' }
];
<Dropdown
value={this.state.selectValue}
data = {sports}
style={styles.Dropdown1}
onChangeText = {this.onChangeHandler}
><Dropdown>
}