Vue2 set a variable from an api callback - vue.js

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!

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;
}

getter/state are not calling on initial time vuejs

I am trying to return a function into computed property but on page refresh getter or state does not load the data into computed property. How can I resolve this ? I did try async await into computed property too it doesn't work. Please guide.
export default {
data(){
return {
isLoading: false,
}
},
async created(){
await this.profile()
},
methods: {
async profile(){
this.isLoading = true;
return await Promise.all([
this.$store.dispatch('class'),
this.$store.dispatch('list')
]).finally(() => {
this.isLoading = false;
})
}
},
computed: {
getItem() {
console.log(this.$store.getters); //This records did not load at first time after rerouting it does work
return () => this.$store.getters.listItem;
}
}
}
I can't figure out why you need to return a function from the computed value.
However, using the computed value in your template will work.
<template>
<div>
{{ getItem() }}
</div>
</template>
But if you want to see your console log, you can use a local variable to force the Vue to watch changes.
computed: {
getItem() {
const lst = this.$store.getters.listItem;
console.log(lst);
return () => lst;
},
},
I think it is better to use the store value directly.
computed: {
getItem() {
return this.$store.getters.listItem;
},
},

How to run a specific function if the component has been called by a specific component and not by other components in Vue?

I have a component called select-diagnosis which is used by many different components.
When select-diagnosis is called by a specific component called PtdTreatment, it needs to run a specific function inside the fetchDiagnosis function, while when called by other components it will not run that specific function.
The fetchDiagnosis needs to understand that select-diagnosis component has been called by the PtdTreatment component.
How to do something like that?
This is the code from PtdTreatment component:
<el-form-item
label="diagnosis"
prop="dimission_diagnosis"
v-if="form.data_dimission">
<select-diagnosis
v-model="form.diagnosis_dimission"
:defaultValue="_.get(record, 'clean_diagnosis_dimission')"
/>
</el-form-item>
And this is the select-diagnosis component:
<template>
<el-select
v-bind="$attrs"
:value="value"
#change="onChange"
#clear="onClear"
clearable
filterable
remote
:remote-method="fetchDiagnosis"
:loading="loadingSelect"
>
<el-option
v-for="item in items"
:key="`diagnosis-${item.id}`"
:label="item.code + ' - ' + item.description"
:value="item.code"
>
</el-option>
</el-select>
</template>
<script>
export default {
name: "SelectDiagnosis",
inheritAttrs: false,
props: ["value", "defaultValue"],
data() {
return {
loadingSelect: false,
items: []
};
},
methods: {
fetchDiagnosis(query) {
const valid = query !== "" && query.length > 2;
if (!valid) return;
this.loadingSelect = true;
let params = { string: query };
axios
.get("/config/diagnosi", { params })
.then(({ data }) => {
//pseudo code
// if this component is called by **select-diagnosis** then do this
this.items = data.filter(diagnosi => {
const code = diagnosi.codice.replace(/\b0+/g, "");
if (code.length >= 4) {
return diagnosi;
}
});
// else do this
this.items = data;
})
.finally(() => (this.loadingSelect = false));
},
onChange(x) {
this.$emit("input", x);
},
onClear() {
this.$emit("input", null);
this.items = [];
}
},
watch: {
defaultValue: {
immediate: true,
handler(newVal, oldVal) {
if (newVal && oldVal === undefined) {
this.items = [newVal];
this.$emit("input", newVal.codice);
}
}
}
}
};
</script>
There are a number of ways to accomplish this, the two that come to mind immediately use props.
You could pass a filterDiagnoses boolean prop to select-diagnosis. If it's true, run the filter logic.
<select-diagnosis v-model="form.diagnosis_dimission" :defaultValue="_.get(record, 'clean_diagnosis_dimission')" :filterDiagnoses="true" />
You could invert control to the parent function and expose a filterFn callback prop - the parent function passes a function prop to the child that the child will call upon fetching the diagnoses (this feels cleaner and more extensible):
/* in the PtdTreatment component */
/* ... */
methods: {
filterDiagnosis(data) {
// filter data
},
}
/* in the PtdTreatment template */
<select-diagnosis v-model="form.diagnosis_dimission" :defaultValue="_.get(record, 'clean_diagnosis_dimission')" :filterFn="filterDiagnosis" />
/* in the select-diagnosis component */
fetchDiagnosis(query) {
const valid = query !== "" && query.length > 2;
if (!valid) return;
this.loadingSelect = true;
let params = { string: query };
axios
.get("/config/diagnosis", { params })
.then(({ data }) => {
if (this.filterFn) {
this.items = this.filterFn(data);
} else {
this.items = data;
}
})
.finally(() => (this.loadingSelect = false));
},
}
You can set a prop on the child component which specifies the 'identity' of the parent component, then test for that in the child:
<select-diagnosis
v-model="form.diagnosis_dimission"
:defaultValue="_.get(record, 'clean_diagnosis_dimission')"
parent="PtdTreatment"
/>
Then in the child (simplified example):
export default {
props: ["value", "defaultValue", "parent"],
methods: {
fetchDiagnosis(query) {
if (this.parent === "PtdTreatment") {
// Parent-specific code here
}
}
}
}

vuejs not setting data property from arrow function

I got this weird thing going on here:
I have this data property in vue
data() {
return {
currentLat: 'intial Lat',
currentLong: 'intial Long',
};
},
mounted() {
this.getCurrentLocation();
},
methods: {
getCurrentLocation() {
navigator.geolocation.getCurrentPosition((position) => {
this.currentLat = position.coords.latitude;
this.currentLong = position.coords.longitude;.
console.log(this.currentLat); this prints 41.2111
});
console.log(this.currentLat); this prints 'intial Lat'
},
},
this.currentLat not set in the mount
I dont understand what's happing here! it's so weird!
Here is an example of converting to a promise and using async/await:
async getCurrentLocation() {
const position = await new Promise(resolve => {
navigator.geolocation.getCurrentPosition(position => resolve(position))
});
this.currentLat = position.coords.latitude;
this.currentLong = position.coords.longitude;
console.log(this.currentLat); // shouldn't print initial value
},
Your code is valid, the callback arrow function is asynchronous (it's not executed immediately) and the call of console.log(this.currentLat); is synchronous which makes it to be executed before the callback context, the property is properly if you use it inside the template it will work fine
Set the values in a callback as follows:
<template>
<div id="app">{{ currentLat }} - {{ currentLong }}</div>
</template>
<script>
export default {
data() {
return {
currentLat: "intial Lat",
currentLong: "intial Long",
};
},
mounted() {
this.getCurrentLocation();
},
methods: {
getCurrentLocation() {
navigator.geolocation.getCurrentPosition(this.setCoordinates);
},
setCoordinates(position) {
this.currentLat = position.coords.latitude;
this.currentLong = position.coords.longitude;
},
},
};
</script>

Relay Moder - Pagination

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.