Vue async call REST API in Vuex store - javascript

My async actions do not run correctly. Im new to Vue and JS and I am not sure what is happening here. I placed some confirm() dialogs within my code, to see which line passed and which not.
Within the ScanView.vue I call my addProduct action. I get the confirm dialog saying "addProduct" and dispatch calles the next callAPI action where I get the "callAPI" confirm dialog but nothing more. Seems like fetch() isnt working at all, because no any other dialog is shown. What am I doing wrong?
ScanView.vue
export default defineComponent({
name: "Home",
methods: {
scanEan() {
// QR Code Scanner Logic
this.$store.dispatch("addProduct", ean);
}
});
main.js
const store = new Vuex.Store({
state: {
products: [{
name: 'Produkt',
ean: '123',
amount: '1',
smallImageUrl: 'smImage',
mediumImageUrl: 'mdImage',
largeImageUrl: 'lgImage',
expiration: []
}]
},
mutations: {
addProduct(state, product) {
state.products.unshift(product);
}
},
actions: {
addProduct(context, ean) {
confirm("addProduct: " + ean);
context.dispatch('callAPI', ean);
},
callAPI(context, ean) {
confirm("callAPI: ");
fetch("https://world.openfoodfacts.org/api/v0/product/" + ean + ".json") //
.then(response => {
confirm("reesponse");
return response.json();
}
) //
.then(data => {
confirm("data: " + data);
context.dispatch('saveProduct', data);
});
},
saveProduct(context, data) {
confirm("saveProduct: ");
const name = data.product.product_name;
const ean = data.code;
const smImage = data.product.image_front_thumb_url;
const mdImage = data.product.image_front_small_url;
const lgImage = data.product.image_front_url;
const expiration = new Array();
const date = new Date(data.product.expiration_date);
expiration.push(date);
const product = new Product(
name,
ean,
smImage,
mdImage,
lgImage,
expiration
)
confirm("Produktdata: " + product);
context.commit('addProduct', product);
}
}
});
app.use(store);
EDIT
I build a simulate button for better testing. QR Scanning does not work in Browser.
Result It does work in Browser. But not on my emulator or android device. Seems like fetch() isnt the right way with ionic-vue. If I catch the error I got TypeError: Failed to fetch...
<template>
<button #click="simulateScan">Simulate Scan</button>
</template>
<script>
export default {
methods: {
simulateScan() {
this.$store.dispatch('addProduct', 737628064502);
}
}
};
</script>

Final Solution
fetch() does not work on android. You have to use something like cordova-http, capacitor-http, ionic-http or else. I used capacitorcommunity-http.
npm install #capacitor-community/http
npx cap sync
import { Http } from '#capacitor-community/http';
[...]
callAPI(context, ean) {
var eanurl = "https://world.openfoodfacts.org/api/v0/product/" + ean + ".json";
Http.get({ url: eanurl}) //
.then(response => {
return response.data;
}
) //
.then(data => {
console.log(data);
context.dispatch('saveProduct', data);
}).catch(error => confirm(error));
},
[...]

Related

Load multipage PDF into vue.js page using vue-pdf

I'm trying to load a multipage pdf from an API into vue.js using the vue-pdf library.
I have can successfully load the pdf and render the 1st page as described in this earlier question here
I've followed the instructions given in The Documentation and tried a with / without using a loadingTask and using different options for the number of pages.
I've attached the code below. this version shows no page number and no pdf. The console has the "loadPdf called" and "success" log messages and no errors.
<template>
<div>
<h3>page count is {{ numPages }}</h3>
<pdf v-for="i in numPages" :key="i" :page="i" :src="pdfsrc"></pdf>
</div>
</template>
<script>
import pdf from "vue-pdf";
import axios from "axios";
import ActiveDirectoryService from "#/services/ActiveDirectoryService.js";
export default {
components: {
pdf
},
props: {
exportId: String
},
data() {
return {
pdfsrc: null,
numPages: null,
objectUrl: null
};
},
mounted() {
if (this.pdfsrc === null) return;
this.pdfsrc.promise.then(pdf => {
this.numPages = pdf.numPages;
});
},
methods: {
loadPdf() {
console.log("loadPdf called");
return axios
.get(`${process.env.VUE_APP_API_INTRANET}/pdf`, {
responseType: "blob"
})
.then(response => {
console.log("Success", response);
const blob = new Blob([response.data]);
const objectUrl = URL.createObjectURL(blob);
this.pdfsrc = pdf.createLoadingTask(objectUrl);
})
.catch(console.error); //
},
clearBuffer() {
if (this.objectUrl !== null) {
URL.revokeObjectURL(this.objectUrl);
this.pdfsrc = null;
this.objectUrl = null;
}
}
},
watch: {
exportId: function(oldVal, newVal) {
console.log("id Changed api for pdf.");
this.loadPdf();
}
},
created() {
console.log("created with id ", this.exportId);
this.loadPdf();
},
beforeDestroy() {
this.clearBuffer();
}
};
</script>
<style scoped></style>
I'm testing with a small 3 page pdf at 46kb in total. The closest I have come to getting this to work is to hard code the number of pages to 3 which renders the pdf correctly. eg
<pdf v-for="i in 3" :key="i" :page="i" :src="pdfsrc"></pdf>
It works but obviously not a solution!
I've also tried other ways to load 'numPages' such as adding this to pdf
#num-pages="numPages = $event"
and using a computed that returns this.pdf.numPages
but so far all have failed.
I've also tried moving the promise to get numpages into the loadPdf method like so
.then(response => {
console.log("Success", response);
const blob = new Blob([response.data]);
const objectUrl = URL.createObjectURL(blob);
this.pdfsrc = pdf.createLoadingTask(objectUrl);
this.pdfsrc.promise.then(pdf => {
this.numPages = pdf.numPages;
});
})
.catch(console.error); //
which also doesn't work.
Update
I've peppered the code with log statements as requested. All looks like it should be working.
.then(response => {
console.log("Success", response);
const blob = new Blob([response.data]);
const objectUrl = URL.createObjectURL(blob);
this.pdfsrc = pdf.createLoadingTask(objectUrl);
console.log("this.pdfsrc.promise", this.pdfsrc.promise);
this.pdfsrc.promise.then(pdf => {
console.log("pdf in callback", pdf);
this.numPages = pdf.numPages;
console.log(
`this.numPages: ${this.numPages} pdf.numPages: ${pdf.numPages} `
);
console.log(this);
});
})
Update 2
I've cleaned up my example and upon removing this line
#num-pages="numPages = $event"
from the template it all started working! I've traced back through the changes in code and it seems that wrapping the object url in a loadingTask and moving the promise to the loadPdf() method is what fixed it.

How to call a composition function from another composition in VueJS

I've been experimenting with the new composition-api in VueJS and am not sure how to solve a problem. I'm looking for some advice on how to properly implement a solution. This wasn't a problem when everything was vuex-based since you can dispatch an action to another module without a problem. However, I'm struggling to find a solution for the composition implementation.
Problem:
Component calls a CompositionA's function.
CompositionA triggers a login function.
On CompositionA's login success/failure response I would like to call a CompositionB function. (CompositionB contains data and logic for showing a snackbar that's used across the site)
The problem is that it is necessary to inject the snackbar dependency in every component rather than have it be instantiated/mounted from CompositionA. Current solution is to this effect:
Component.vue:
// template calls login(credentials) method
import { useCompositionA } from '#/compositions/compositionA'
import { useCompositionB } from '#/compositions/compositionB'
export default {
name: 'Component',
setup(props, context) {
const { login } = useCompositionA(props, context, useCompositionB(props, context))
return {
login
}
},
}
compositionA.js:
export const useAuth = (props, context, snack) => {
const login = async (credentials) => {
try {
return await loginWithEmailPassword(credentials)
snack.show({text: 'Welcome back!'})
} catch (err) {
snack.show({text: 'Failed to login'})
}
}
return { login }
}
compositionB.js:
export const useSnack = (props, context) => {
const snack = reactive({
color: 'success',
text: null,
timeout: 6000,
visible: true,
})
const snackRefs = toRefs(snack)
const show = ({ text, timeout, color }) => {
snackRefs.text.value = text
snackRefs.timeout.value = timeout || 6000
snackRefs.color.value = color || 'success'
snackRefs.visible.value = true
}
return {
...snackRefs,
show
}
}
Would be nice if something like below existed, but I'm finding that the properties aren't reactive in CompositionB if it's used from CompositionA (method gets called but snackbar doesn't show up). My understanding is that Vue isn't injecting CompositionB into the Component, so I'm just running another instance of CompositionB inside CompositionA. What am I doing something wrong? What's the proper solution here?
compositionA.js (not working):
import { useCompositionB } from '#/compositions/compositionB'
export const useAuth = (props, context) => {
const login = async (credentials) => {
const { show } = useCompositionB()
try {
return await loginWithEmailPassword(credentials)
show({text: 'Welcome back!'})
} catch (err) {
show({text: 'Failed to login'})
}
}
return { login }
}
Thanks in advance,
As expected it was due to the Component referencing its own local copy of CompositionB*. Solution is actually to bring the state of your compositions into the global scope according to:
https://vueschool.io/articles/vuejs-tutorials/state-management-with-composition-api/
Something like this:
compositionB.js:
const snack = reactive({
color: 'success',
text: null,
timeout: 6000,
visible: true,
})
export const useSnack = (props, context) => {
const snackRefs = toRefs(snack)
const show = ({ text, timeout, color }) => {
snackRefs.text.value = text
snackRefs.timeout.value = timeout || 6000
snackRefs.color.value = color || 'success'
snackRefs.visible.value = true
}
return {
...snackRefs,
show
}
}
Works like a charm.
Only caveat I found initially was a composition-api error:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function.
This was easily solved by mounting the composition-api first thing in main.js as per solution here:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function
I think this won't be a problem with vue3 comes out. Hope this helps someone.

Request with Axios. How to solve the async problem?

I have this _id.vue page on my Nuxt.js project:
<template>
<div class="wrapper">
<HeaderApp>
<DivHeaderMenu>
</DivHeaderMenu>
</HeaderApp>
<CenterContentDinamicFirmenistorieApp>
</CenterContentDinamicFirmenistorieApp>
<FooterApp>
</FooterApp>
</div>
</template>
<script>
//company_history
import axios from 'axios';
import HeaderApp from '~/components/HeaderApp';
import FooterApp from '~/components/FooterApp';
import CenterContentDinamicFirmenistorieApp from '~/components/CenterContentDinamicFirmenistorieApp'
import DivHeaderMenu from '~/components/DivHeaderMenu';
import Pixelperfect from '~/components/Pixelperfect';
export default{
async fetch ({ store, params, redirect, app}) {
return axios.get('http://seo-gmbh.eu/json/api_sunds.php?action=get_pages&url=company_history')
.then((res) => {
store.commit('company_history/init_data_for_firmenistorie', res.data);
})
},
async validate({store, params, redirect}) {
const urlData = store.state.company_history.dbFirmenstorie.dbFirmenistorieSortArrayData;
let resultArray = false;
for (let i = 0; i < urlData.length; i++) {
if(params.id === urlData[i].toString()){
return resultArray = urlData[i];
}
}
if(resultArray == false){
return redirect('/Firmenistorie');
}
},
head () {
return {
title: this.$store.state.company_history.dbFirmenstorie.dbFirmenistorieData.data.meta.title,
meta: [
{description: this.$store.state.company_history.dbFirmenstorie.dbFirmenistorieData.data.meta.description}
]
}
},
components:{
HeaderApp,
FooterApp,
CenterContentDinamicFirmenistorieApp,
DivHeaderMenu,
Pixelperfect
},
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
My task is to get a redirect when I get the 404th error on the dynamic page (_id). The whole implementation works fine if I go through nuxt-link (s) to similar pages - the 404th error works fine if I enter an incorrect URL in the address bar. But the problem appears if I'm already on a working page - I reload it. Instead of loading the same page again, I get the 404th error and redirect as a result. This happens because in this particular case I do not receive data from the store
My question is: How can I solve this (asynchronous, as I understand it) problem? (I tried everything that is possible - nothing helps).
My Vuex repository looks rather piled up - but just in case, I'll throw its code for a better understanding of the problem:
export const state = () => ({
dbFirmenstorie: {
dbFirmenistorieData: null,
dbFirmenistorieMaxYearData: null,
dbFirmenistorieMaxDetailsData: null,
dbFirmenistorieSortArrayData: [],
},
});
export const mutations = {
init_data_for_firmenistorie (state, uploadDbFirmenistorieData) {
state.dbFirmenstorie.dbFirmenistorieData = uploadDbFirmenistorieData;
state.dbFirmenstorie.dbFirmenistorieData.data.content_json = JSON.parse(state.dbFirmenstorie.dbFirmenistorieData.data.content_json);
state.dbFirmenstorie.dbFirmenistorieData.data.meta = JSON.parse(state.dbFirmenstorie.dbFirmenistorieData.data.meta);
for (let i = 0; i < state.dbFirmenstorie.dbFirmenistorieData.data.company_history_data.length; i++) {
if(state.dbFirmenstorie.dbFirmenistorieSortArrayData.indexOf( Number( state.dbFirmenstorie.dbFirmenistorieData.data.company_history_data[i].company_history_from_year )) == -1 ){
state.dbFirmenstorie.dbFirmenistorieSortArrayData.push(Number(state.dbFirmenstorie.dbFirmenistorieData.data.company_history_data[i].company_history_from_year));
}
if(state.dbFirmenstorie.dbFirmenistorieMaxYearData < Number(state.dbFirmenstorie.dbFirmenistorieData.data.company_history_data[i].company_history_from_year)){
state.dbFirmenstorie.dbFirmenistorieMaxYearData = Number(state.dbFirmenstorie.dbFirmenistorieData.data.company_history_data[i].company_history_from_year);
state.dbFirmenstorie.dbFirmenistorieMaxYearData = Number(state.dbFirmenstorie.dbFirmenistorieData.data.company_history_data[i].company_history_from_year);
state.dbFirmenstorie.dbFirmenistorieMaxDetailsData = state.dbFirmenstorie.dbFirmenistorieData.data.company_history_data[i];
}
}
function sortNumber(a, b) {
return b - a;
}
state.dbFirmenstorie.dbFirmenistorieSortArrayData.sort(sortNumber);
}
};
I am pretty sure that if you start using catch() with axios as everyone should always do, you will be able to handle all non 200 responses just fine. Which means 404, 40x, 50x, etc...
axios
.get("https://example.com")
.then(res => console.log(res))
.catch(e => console.log(e))

Vanilla JS vs React Class Binding for Listener Functions

I am following some api docs where the only code examples are in vanilla JS but I am trying to use them in React Native. They give fully functional React Native apps for reference but I can't figure out how to repurpose the methods for my needs.
In the api docs it gives the example:
ConnectyCube.videochat.onCallListener = function(session, extension) {
// here show some UI with 2 buttons - accept & reject, and by accept -> run the following code:
var extension = {};
session.accept(extension);
};
ConnectyCube is an module import and I need to use this particular method in React Native. In the app they provide as an example, it looks like this in a class component:
class AppRoot extends React.Component {
componentDidMount() {
ConnectyCube.init(...config)
this.setupListeners();
}
setupListeners() {
ConnectyCube.videochat.onCallListener = this.onCallListener.bind(this);
ConnectyCube.videochat.onUserNotAnswerListener = this.onUserNotAnswerListener.bind(this);
ConnectyCube.videochat.onAcceptCallListener = this.onAcceptCallListener.bind(this);
ConnectyCube.videochat.onRemoteStreamListener = this.onRemoteStreamListener.bind(this);
ConnectyCube.videochat.onRejectCallListener = this.onRejectCallListener.bind(this);
ConnectyCube.videochat.onStopCallListener = this.onStopCallListener.bind(this);
ConnectyCube.videochat.onSessionConnectionStateChangedListener = this.onSessionConnectionStateChangedListener.bind(this);
}
onCallListener(session, extension) {
console.log('onCallListener, extension: ', extension);
const {
videoSessionObtained,
setMediaDevices,
localVideoStreamObtained,
callInProgress
} = this.props
videoSessionObtained(session);
Alert.alert(
'Incoming call',
'from user',
[
{text: 'Accept', onPress: () => {
console.log('Accepted call request');
CallingService.getVideoDevices()
.then(setMediaDevices);
CallingService.getUserMedia(session).then(stream => {
console.log(stream)
localVideoStreamObtained(stream);
CallingService.acceptCall(session);
callInProgress(true);
});
}},
{
text: 'Reject',
onPress: () => {
console.log('Rejected call request');
CallingService.rejectCall(session);
},
style: 'cancel',
},
],
{cancelable: false},
);
}
onUserNotAnswerListener(session, userId) {
CallingService.processOnUserNotAnswer(session, userId);
this.props.userIsCalling(false);
}
onAcceptCallListener(session, userId, extension) {
CallingService.processOnAcceptCallListener(session, extension);
this.props.callInProgress(true);
}
onRemoteStreamListener(session, userID, remoteStream){
this.props.remoteVideoStreamObtained(remoteStream, userID);
this.props.userIsCalling(false);
}
onRejectCallListener(session, userId, extension){
CallingService.processOnRejectCallListener(session, extension);
this.props.userIsCalling(false);
this.props.clearVideoSession();
this.props.clearVideoStreams();
}
onStopCallListener(session, userId, extension){
this.props.userIsCalling(false);
this.props.callInProgress(false);
this.props.clearVideoSession();
this.props.clearVideoStreams();
CallingService.processOnStopCallListener(session, extension);
}
onSessionConnectionStateChangedListener(session, userID, connectionState){
console.log('onSessionConnectionStateChangedListener', userID, connectionState);
}
render() {
console.log('hey');
return <AppRouter />
}
}
function mapDispatchToProps(dispatch) {
return {
videoSessionObtained: videoSession => dispatch(videoSessionObtained(videoSession)),
userIsCalling: isCalling => dispatch(userIsCalling(isCalling)),
callInProgress: inProgress => dispatch(callInProgress(inProgress)),
remoteVideoStreamObtained: remoteStream => dispatch(remoteVideoStreamObtained(remoteStream)),
localVideoStreamObtained: localStream => dispatch(localVideoStreamObtained(localStream)),
clearVideoSession: () => dispatch(clearVideoSession()),
clearVideoStreams: () => dispatch(clearVideoStreams()),
setMediaDevices: mediaDevices => dispatch(setMediaDevices(mediaDevices)),
setActiveVideoDevice: videoDevice => dispatch(setActiveVideoDevice(videoDevice))
}
}
export default connect(null, mapDispatchToProps)(AppRoot)
I want to set up the listeners but I am not using classes like the one in the component above called CallingService or using the same redux actions - I'm taking a functional approach. When I paste the code from the docs in to a service which is just a normal function, I get the error:
Cannot set property 'onCallListener' of undefined.
Any ideas welcome!
componentDidMount() {
document.addEventListener("keyup",this.login,false);
}
login = (event) => {
console.log('i have been activated on keyup event from the componentDidMount()');
};

How to set value in Vuex/Nuxt

I am new to Vuex and Nuxt.
I would like to use vuex to fetch dropbox filestructure and store them.
Here is my code. the console.log seems to work fine. it prints out something like below.
But the structure still turns out to be [] when i use in index.vue
[ { '.tag': 'file',
name: 'Document.docx',
path_lower: '/posts/document.docx',
path_display: '/posts/Document.docx',
id: 'id:H_6Dhj1r7cEAAAAAAAAXlQ',
client_modified: '2018-09-02T14:23:05Z',
server_modified: '2018-09-02T14:23:06Z',
rev: '5e5cab150',
size: 11366,
content_hash: 'd26bb0382752820694d31f42e82e31ef72bed683b90e02952ea09125264d4124' },
{ '.tag': 'file',
name: '2013-5-17-first-post.md',
path_lower: '/posts/2013-5-17-first-post.md',
path_display: '/posts/2013-5-17-first-post.md',
id: 'id:H_6Dhj1r7cEAAAAAAAAXlg',
client_modified: '2018-09-02T14:25:38Z',
server_modified: '2018-09-02T14:25:38Z',
rev: '6e5cab150',
size: 136,
content_hash: '3b8d60de425e8280d55e45d7359cd3290abc5bc3b0bb6831b09a6da0d3cb6a12' } ]
the code is like below
import "isomorphic-fetch"
import {
Dropbox
} from "dropbox";
import {
DropboxTeam
} from "dropbox";
export const state = () => ({
structure: []
});
export const mutations = {
setStucture(state, structure) {
state.structure = structure.slice();
console.log(state.structure);
// console.log(structure.slice());
}
};
export const actions = {
async nuxtServerInit({ commit }) {
let accessToken = "XXXXX"
let dropbox = new Dropbox({
accessToken: accessToken
});
dropbox.filesListFolder({path: '/posts'})
.then(response => {
const structure = response.entries;
commit("setStucture", structure);
})
.catch(error => {
console.log(error);
});
}
};
Can I get some help. Thank you!
add await to dropbox.filesListFolder({path: '/posts'}) like below turned out to be the right answer.
await dropbox.filesListFolder({path: '/posts'})

Categories