Quasar upload file from axios - vue.js

I have a form with multiple inputs that includes file input too. Now, I want to pass these data on the onSubmit function. but, there is an issue, in the quasar documentation, I didn't see instruction about file upload by Axios in the script part.
I read Uploader in the quasar doc and also I read this one from Stackoverlow, But I didn't work for me.
Also, this is my templates code:
<template>
<div class="q-pa-md q-mt-md">
<q-card class="my-card">
<q-form
#submit="onSubmit"
class="q-gutter-md"
>
<div class="row justify-center">
<q-uploader
label="Upload your music"
color="purple"
accept=".mp3"
:max-file-size="20000000"
square
flat
#add="file_selected"
bordered
/>
</div>
<div class="row justify-center">
<q-btn label="Edit" type="submit" color="primary" v-if="song_id" class="q-ma-md" />
<q-btn label="Add" type="submit" color="primary" v-else class="q-ma-md" />
<q-btn label="Cancel" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</q-card>
</div>
</template>
And the methods part:
file_selected: function (file) {
console.log(file)
this.selected_file = file[0]
this.check_if_document_upload = true
},
onSubmit: function () {
const url = '/core/v1/api/songs/upload'
const fileData = new FormData()
fileData.append('file_data', this.selected_file)
fileData.append('song_id', this.song_id)
this.$axios.post(url, fileData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(function () {
console.log('SUCCESS!!')
})
.catch(function () {
console.log('FAILURE!!')
})
And data part:
data: () => ({
selected_file: '',
check_if_document_upload: false,
song_id: '',
song_data: {
status: true
},
dashData: []
}),

If quasar uploads isn't working for you and you are using state management vuex, you could attempt writing custom code to accomplish what you want. Try this for sending the post request using axios
createEvents({ commit }, payload) {
const stuff = {
title: payload.title,
location: payload.location,
description: payload.description,
image = payload.image;
};
let formData = new FormData();
bodyFormData.set('title', stuff.title); //done for the text data
formData.append("imageUrl", stuff.image); //done for file data
axios
.post({
method: 'post',
url: 'myurl',
data: formData,
headers: {'Content-Type': 'multipart/form-data' }
})
.then(response => {
commit("createEvents", response.data);
})
.catch(err => err.data);
}
}
And for the submit function(method), it should look something like this
createEvent(){
const newEvent = {
title: '',
location: '',
description: '',
image: this.image,
};
this.$store.dispatch("createEvents", newEvent);
};
finally, the form itself in your code. The image should be referenced with a simple
<input type='file' ref='image'> and the rest of your form can be normal
<form>
<input type='text' v-model='text'>
<-- more of the same -->
<input type='file' ref='image'>
// prevent is to keep the page from reloading when the form gets submitted,
// just a precaution measure
<button type=submit #click.prevent=createEvent()>submit</button>
</form>
Hope this helped

I found my issue. I should change #add to #added in the template.
<template>
<div class="q-pa-md q-mt-md">
<q-card class="my-card">
<q-form
#submit="onSubmit"
class="q-gutter-md"
>
<div class="row justify-center">
<q-uploader
label="Upload your music"
color="purple"
accept=".mp3"
:max-file-size="20000000"
square
flat
#added="file_selected"
bordered
/>
</div>
<div class="row justify-center">
<q-btn label="Edit" type="submit" color="primary" v-if="song_id" class="q-ma-md" />
<q-btn label="Add" type="submit" color="primary" v-else class="q-ma-md" />
<q-btn label="Cancel" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</q-card>
</div>
</template>

If you want to keep the QUploader functionalities, status changes, upload progress, in my case I made the component extension and it works fine, maybe it is not efficient because I had to add my own methods: upload, __uploadFiles.
Don't add method to override __runFactory, since I omitted batch load option, and I will always use factory as function.
QUploader source:
Part Code from Quasar Components Uploader -> xhr mixin.js
upload () {
if (this.canUpload === false) {
return
}
const queue = this.queuedFiles.slice(0)
this.queuedFiles = []
if (this.xhrProps.batch(queue)) {
this.__runFactory(queue)
}
else {
queue.forEach(file => {
this.__runFactory([ file ])
})
}
},
__runFactory (files) {
this.workingThreads++
if (typeof this.factory !== 'function') {
this.__uploadFiles(files, {})
return
}
const res = this.factory(files)
if (!res) {
this.$emit(
'factory-failed',
new Error('QUploader: factory() does not return properly'),
files
)
this.workingThreads--
}
else if (typeof res.catch === 'function' && typeof res.then === 'function') {
this.promises.push(res)
const failed = err => {
if (this._isBeingDestroyed !== true && this._isDestroyed !== true) {
this.promises = this.promises.filter(p => p !== res)
if (this.promises.length === 0) {
this.abortPromises = false
}
this.queuedFiles = this.queuedFiles.concat(files)
files.forEach(f => { this.__updateFile(f, 'failed') })
this.$emit('factory-failed', err, files)
this.workingThreads--
}
}
res.then(factory => {
if (this.abortPromises === true) {
failed(new Error('Aborted'))
}
else if (this._isBeingDestroyed !== true && this._isDestroyed !== true) {
this.promises = this.promises.filter(p => p !== res)
this.__uploadFiles(files, factory)
}
}).catch(failed)
}
else {
this.__uploadFiles(files, res || {})
}
},
__uploadFiles (files, factory) {
const
form = new FormData(),
xhr = new XMLHttpRequest()
const getProp = (name, arg) => {
return factory[name] !== void 0
? getFn(factory[name])(arg)
: this.xhrProps[name](arg)
}
const url = getProp('url', files)
if (!url) {
console.error('q-uploader: invalid or no URL specified')
this.workingThreads--
return
}
const fields = getProp('formFields', files)
fields !== void 0 && fields.forEach(field => {
form.append(field.name, field.value)
})
let
uploadIndex = 0,
uploadIndexSize = 0,
uploadedSize = 0,
maxUploadSize = 0,
aborted
xhr.upload.addEventListener('progress', e => {
if (aborted === true) { return }
const loaded = Math.min(maxUploadSize, e.loaded)
this.uploadedSize += loaded - uploadedSize
uploadedSize = loaded
let size = uploadedSize - uploadIndexSize
for (let i = uploadIndex; size > 0 && i < files.length; i++) {
const
file = files[i],
uploaded = size > file.size
if (uploaded) {
size -= file.size
uploadIndex++
uploadIndexSize += file.size
this.__updateFile(file, 'uploading', file.size)
}
else {
this.__updateFile(file, 'uploading', size)
return
}
}
}, false)
xhr.onreadystatechange = () => {
if (xhr.readyState < 4) {
return
}
if (xhr.status && xhr.status < 400) {
this.uploadedFiles = this.uploadedFiles.concat(files)
files.forEach(f => { this.__updateFile(f, 'uploaded') })
this.$emit('uploaded', { files, xhr })
}
else {
aborted = true
this.uploadedSize -= uploadedSize
this.queuedFiles = this.queuedFiles.concat(files)
files.forEach(f => { this.__updateFile(f, 'failed') })
this.$emit('failed', { files, xhr })
}
this.workingThreads--
this.xhrs = this.xhrs.filter(x => x !== xhr)
}
xhr.open(
getProp('method', files),
url
)
if (getProp('withCredentials', files) === true) {
xhr.withCredentials = true
}
const headers = getProp('headers', files)
headers !== void 0 && headers.forEach(head => {
xhr.setRequestHeader(head.name, head.value)
})
const sendRaw = getProp('sendRaw', files)
files.forEach(file => {
this.__updateFile(file, 'uploading', 0)
if (sendRaw !== true) {
form.append(getProp('fieldName', file), file, file.name)
}
file.xhr = xhr
file.__abort = () => { xhr.abort() }
maxUploadSize += file.size
})
this.$emit('uploading', { files, xhr })
this.xhrs.push(xhr)
if (sendRaw === true) {
xhr.send(new Blob(files))
}
else {
xhr.send(form)
}
}
Result: With AXIOS - Component Vue that extend from QUploader
<script lang="ts">
import { QUploader } from 'quasar';
export default class AxiosUploader extends QUploader {
constructor(props) {
super(props);
}
AxiosUpload() {
if (this.canUpload === false) {
return;
}
const queue = this.queuedFiles.slice(0);
this.queuedFiles = [];
const factory = this.factory(queue);
queue.forEach(file => {
this.workingThreads++;
this.uploadFiles([file], factory);
});
}
uploadFiles(files, factory) {
const form = new FormData(),
headers = {};
factory.headers.forEach(head => {
headers[head.name] = head.value;
});
factory.formFields.forEach(field => {
form.append(field.name, field.value);
});
form.append(factory.fieldName, files[0], files[0].name);
let uploadIndex = 0,
uploadIndexSize = 0,
uploadedSize = 0,
maxUploadSize = 0,
aborted;
const xhr = this.$axios.post(factory.url, form, {
headers,
onUploadProgress: (e: ProgressEvent) => {
if (aborted === true) {
return;
}
const loaded = Math.min(maxUploadSize, e.loaded);
this.uploadedSize += loaded - uploadedSize;
uploadedSize = loaded;
let size = uploadedSize - uploadIndexSize;
for (let i = uploadIndex; size > 0 && i < files.length; i++) {
const file = files[i],
uploaded = size > file.size;
if (uploaded) {
size -= file.size;
uploadIndex++;
uploadIndexSize += file.size;
this.__updateFile(file, 'uploading', file.size);
} else {
this.__updateFile(file, 'uploading', size);
return;
}
}
}
});
this.xhrs.push(xhr);
this.$emit('uploading', { files, xhr });
xhr
.then(res => {
this.uploadedFiles = this.uploadedFiles.concat(files);
files.forEach(f => {
this.__updateFile(f, 'uploaded');
});
this.$emit('uploaded', { files, xhr });
})
.catch(err => {
aborted = true;
this.uploadedSize -= uploadedSize;
this.queuedFiles = this.queuedFiles.concat(files);
files.forEach(f => {
this.__updateFile(f, 'failed');
});
this.$emit('failed', { files, xhr });
})
.finally(() => {
this.workingThreads--;
this.xhrs = this.xhrs.filter(x => x !== xhr);
});
files.forEach(file => {
this.__updateFile(file, 'uploading', 0);
file.xhr = xhr;
file.__abort = () => {
xhr.abort();
};
maxUploadSize += file.size;
});
this.$emit('uploading', { files, xhr });
this.xhrs.push(xhr);
}
}
</script>
The component to use is AxiosUploader instead of q-uploader,
and instead of calling the upload () method, I call the AxiosUpload method. You could adapt to your needs

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

How to access uploaded image using vue.js

Here I have written a code for multi upload images. And the functionality i dont know where I am going wrong. like while i am uploading an image. So in console terminal I am seeing only image name not image. So please tell me where I am going wrong I want to save the image in backend but I am getting only image name in vue code. So please tell me where I am going wrong. Please help me
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<h2>Multiple Files</h2>
<hr/>
<label>
<span>Files</span>
<input type="file" multiple #change="handleFileUploads($event)" />
<ul v-if="files.length">
<li v-for="(name, i) in filesNames" :key="i">{{ name }}</li>
</ul>
</label>
<div>
<img v-for="image in images" :src="image" />
</div>
<br />
<button #click="submitFiles()">Submit</button>
</div>
vue.js
Vue.config.productionTip = false;
new Vue({
el: '#app',
data() {
return {
files: [],
images: [],
}
},
computed: {
filesNames() {
const fn = []
for (let i = 0; i < this.files.length; ++i) {
fn.push(this.files.item(i).name)
}
return fn
}
},
methods: {
handleFileUploads(event) {
this.files = event.target.files;
this.images = [...this.files].map(URL.createObjectURL);
},
// how to access img_fna or sal to backend in from this snippet of code.
submitForm: function(){
const img_fna = []
for (let i = 0; i < this.files.length; ++i) {
let file = this.files[i];
img_fna.push(file)
}
var reader;
var file;
var i;
const sal = []
for (i = 0; i < files_s.length; i++) {
file = files_s[i];
reader = new FileReader();
reader.onload = (function(file) {
return function(e) {
sal.push(e.target.result);
};
})(file);
reader.readAsDataURL(file);
}
axios({
method : "POST",
url: "{% url 'service-ad' %}", //django path name
headers: {'X-CSRFTOKEN': '{{ csrf_token }}',
'Content-Type': 'application/json'},
data : {
"images": sal,
"image_url": img_fna,
},//data
}).then(response => {
this.success_msg = response.data['msg'];
}).catch(err => {
this.err_msg = err.response.data['err'];
});
}
}
})
If I understand you correctly, you want to post your images list to backend, but you don't know how to get data from files like this:
enter image description here
and send data to backend. And in backend, you can only see image name, am i right?
If so, here is my example code:
async submitFile() {
let filelist = [];
for (let i = 0; i < this.files.length; ++i) {
filelist.push(
await (async (cur) => {
return new Promise((resolve, reject) => {
const fr = new FileReader();
fr.onload = () => {
resolve(fr.result);
};
fr.readAsDataURL(cur);
});
})(this.files.item(i))
);
}
let formData = new FormData();
formData.append("files", filelist);
axios.post('/multiple-files', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(function() {
console.log('SUCCESS!!');
})
.catch(function() {
console.log('FAILURE!!');
});
},
The focus is to use FileReader.readAsDataURL to get the image as base64 encoded data. Then you have image data in the backend like this:
enter image description here

ERROR in conditions to show a loading bar

I hope they are fine, my problem is that it does not take my conditions that when my data is not loaded in this case coupons, it does not show and only shows the bar loading, what I achieve is what is in the gif below
So in my interface State I have this property loadingCoupons: boolean,
In my state I have the property created above with value true... loadingCoupons: true
so in my updateCoupons method I put it false so that when loading the loading bar is removed
updateCoupons = async () => {
const coupons = await this.props.coupons.get(this.state.page, this.state.limit)
const couponsUsed = await this.props.coupons.getUsed(this.state.page, this.state.limit)
const couponsExpired = await this.props.coupons.getExpired(this.state.page, this.state.limit)
this.setState({
coupons,
couponsUsed,
couponsExpired,
loading: false,
loadingCoupons: false
})
}
In my render is the property loadingCoupons
So the code where I execute this, that everything is done is this, why my conditions are not met? What you should do is that when changing the tab, load and when you have the data loaded, show it, currently it always shows the loading bar
type IPathParams = {}
type IProps = RouteComponentProps<IPathParams> & {
coupons: Coupon
providerModel: ProviderModel
cartModel: CartModel
user: User
}
interface State {
msg: any
coupons: any
couponsUsed: any
couponsExpired: any
limit: any
loading: boolean
page: any
views: any
valueInput:string
spinner:boolean
couponsActvive:boolean
alert:boolean
couponValidate:any
message:string
alertDelete:boolean
messageDelete:string
segmentCoupons:any
loadingCoupons: boolean
}
class Coupons extends React.PureComponent<IProps, State> {
state: State = {
msg: { msg: null, active: false },
coupons: [],
couponsUsed: [],
couponsExpired: [],
limit: 10,
loading: true,
page: 0,
views: {
valid: { name: 0, active: true },
used: { name: 1, active: false },
expired: { name: 2, active: false }
},
valueInput:'',
spinner:false,
couponsActvive:false,
alert:false,
couponValidate:false,
message:'',
alertDelete:false,
messageDelete:'',
segmentCoupons:'',
loadingCoupons: true
}
async componentDidMount() {
this.updateCoupons()
}
updateCoupons = async () => {
const coupons = await this.props.coupons.get(this.state.page,
this.state.limit)
const couponsUsed = await this.props.coupons.getUsed(this.state.page,
this.state.limit)
const couponsExpired = await
this.props.coupons.getExpired(this.state.page,
this.state.limit)
this.setState({
coupons,
couponsUsed,
couponsExpired,
loading: false,
loadingCoupons: false
})
}
goToCart = () => {
// this.props.history.replace('/cart')
window.location.href = "/cart";
}
goToHome = () => {
this.props.history.replace('/home')
}
groupedCoupons = (coupons: any) => {
return groupBy(coupons, (coupon: any) => {
return coupon._id
})
}
activateCoupon = async (id: string) => {
const result = await this.props.coupons.activate(id)
const msg = !result.length && Object(result)
if (!result.length && msg) {
msg.active = true
msg.msg = Object(result).description
if (Object(result).code) {
msg.header = "Felicitaciones!!!"
} else {
msg.header = "Upps"
}
this.setState({
msg
})
} else if (result.length) {
msg.msg = 'Activación satisfactoria.'
msg.active = true
this.setState({
msg
})
}
const coupons = await this.props.coupons.get(this.state.page,
this.state.limit)
this.setState({
coupons,
loading: false,
loadingCoupons: false
})
}
renderCouponList(key: any, coupons: any) {
const { views } = this.state
return (
<div key={key} className="list-orders">
<div className="list">
{coupons.map((coupon: any) => {
const { _id, amount, minimum_amount, date_expires, discount_type,
code, image } = coupon
const expire = moment(new Date(date_expires)).format('DD/MM/YYYY')
return (
<Fragment key={_id}>
<div className="coupon">
<IonAvatar className="no-radius">
<img className="img_coupon" src={(image &&
`${process.env.REACT_APP_BFF_IMAGE}coupons/${image.url}`)} />
</IonAvatar>
<div className="provider">
<div className="code">CÓDIGO: #{code}</div>
<div className="expire">VENCE: {expire}</div>
{discount_type === 'porcent' ? (
<div className="amount"><strong>Descuento {amount}%
</strong></div>
) : discount_type === 'amount' && (
<div className="amount"><strong>{asClp(amount)} de
regalo</strong></div>
)}
<div className="minimum_amount">COMPRAS SOBRE
{asClp(minimum_amount)}</div>
</div>
{views.valid.active && (
<div className="button-wrapper">
<button onClick={() =>
this.activateCoupon(_id)}>Activar</button>
</div>
)}
{coupon.couponType === "referal" &&
<div className="button-garbage">
<IonIcon
className="icon"
src={Garbage}
onClick={() => this.deleteCouponReferal(coupon.code)}
/>
</div>
}
</div>
</Fragment>
)
})}
</div>
</div>
)
}
deleteCouponReferal = async (code:any) =>{
const user: any = (await this.props.user.getUser()) || {}
const result = await this.props.coupons.removeCouponReferal(code, user.id)
if(result.status===200){
this.setState({
alertDelete:true,
messageDelete:result.respuesta
})
setTimeout(() => {
window.location.reload()
}, 2000);
}
}
onChangeInput = async (value: any) => {
const val = Number(value)
const { views } = this.state
switch (val) {
case 0:
views.valid.active = true
views.used.active = false
views.expired.active = false
this.setState({
views, segmentCoupons:val
})
await this.updateCoupons()
break;
case 1:
views.valid.active = false
views.used.active = true
views.expired.active = false
this.setState({
views, segmentCoupons:val
})
await this.updateCoupons()
break;
case 2:
views.valid.active = false
views.used.active = false
views.expired.active = true
this.setState({
views, segmentCoupons:val
})
await this.updateCoupons()
break;
default:
break;
}
}
onChangeValidate = (event:any) =>{
this.setState({
valueInput:event
})
}
buttonValidate = async () =>{
const { valueInput } = this.state
const user: any = (await this.props.user.getUser()) || {}
this.setState({
spinner:true,
})
const result = await this.props.coupons.countReferal(valueInput, user.id)
if(result.status===400){
this.setState({
couponValidate:false,
message:result.respuesta
})
}else if(result.status===200){
this.setState({
couponValidate:false,
message:result.respuesta
})
}else if(result.status===404){
this.setState({
couponValidate:true,
message:result.respuesta
})
}else if(result.status===409){
this.setState({
couponValidate:'409',
message:result.respuesta
})
}
this.setState({
valueInput:'',
spinner:Object.keys(result).length > 0 ? false :true,
couponsActvive: Object.keys(result).length > 0 ? true :false,
alert:true,
})
}
render() {
const { history, cartModel } = this.props
const { coupons, couponsExpired, couponsUsed, loading, views,
msg,valueInput,
spinner,alert,
couponValidate, message, alertDelete, messageDelete, segmentCoupons,
loadingCoupons } = this.state
const length = coupons.length
const lengthExpired = couponsExpired.length
const lengthUsed = couponsUsed.length
const grouped = this.groupedCoupons(coupons)
const groupedExpired = this.groupedCoupons(couponsExpired)
const groupedUsed = this.groupedCoupons(couponsUsed)
const productCount = cartModel.getCart().length
return (
<IonPage className="orders-page-cupons">
<IonHeader>
<ToolBar
title="Cupones"
secondaryButtons={[{ type: 'back', onClick: history.goBack }]}
tertiaryButtons={[{ key: 'cart', onClick: this.goToCart, icon:
cartToolbarIcon, badge: productCount }]}
primaryButtons={[{ key: 'home', onClick: this.goToHome, icon:
homeIcon }]}
/>
</IonHeader>
{/* Without items */}
<IonContent>
{msg.active && (
<IonSlides pager={false}>
<IonSlide>
<IonAlert
isOpen={msg.active}
onDidDismiss={() => this.setState({
msg: { active: false, msg: null }
})}
header={msg.header}
message={msg.msg}
buttons={
[{
text: 'Cerrar',
handler: () => this.setState({
msg: { active: false, msg: null }
}),
}]
}
/>
</IonSlide>
</IonSlides>
)}
{alert && (
<IonAlert
isOpen={true}
header={!couponValidate ? '¡Felicitaciones!' : couponValidate ===
'409' ? '' :'Código Incorrecto'}
message={couponValidate ?
message :
message
}
buttons={
[{
text: 'Cerrar',
handler: () => this.setState({
alert:false
}),
}]
}
/>
) }
{loading && <IonProgressBar type="indeterminate"></IonProgressBar>}
{!loading && (
<IonContent>
<IonSegment onIonChange={e => this.onChangeInput(e.detail.value ||
'')}
value={views.valid.active ? views.valid.name :
views.used.active ? views.used.name :
views.expired.active && views.expired.name}>
<IonSegmentButton value={views.valid.name}>
<IonLabel>Disponibles</IonLabel>
</IonSegmentButton>
<IonSegmentButton value={views.used.name}>
<IonLabel>Activos</IonLabel>
</IonSegmentButton>
<IonSegmentButton value={views.expired.name}>
<IonLabel>Canjeados</IonLabel>
</IonSegmentButton>
</IonSegment>
{loadingCoupons && <IonProgressBar type="indeterminate">
</IonProgressBar>}
{views.valid.active && <div className="new-coupons">
<div className="cotainer-coupons-referal">
<div className="title-new-coupons">
Nuevo Cupón
</div>
<div>
<div className="container-input">
<IonInput
value={valueInput}
maxlength={13}
onIonChange={e => this.onChangeValidate(e.detail.value)}>
</IonInput>
</div>
<div className="container-btn-validate" >
<IonButton
onClick={this.buttonValidate}
disabled={valueInput ? false: true}
>Validar
{spinner && <IonSpinner name="crescent" />}
</IonButton>
</div>
</div>
</div>
</div>}
{length > 0 && segmentCoupons === 0 && !loadingCoupons? (
<div className="wrapper-orders">
{Object.keys(grouped).map((key: any) => {
return this.renderCouponList(key, grouped[key])
})}
</div>
) : views.valid.active && (
<div className="without-products">
<IonImg src={withoutCouponsIcon} />
<div className="message">Sin cupones.</div>
</div>
)}
{lengthExpired > 0 && segmentCoupons === 2 && !loadingCoupons? (
<div className="wrapper-orders">
{Object.keys(groupedExpired).map((key: any) => {
return this.renderCouponList(key, groupedExpired[key])
})}
</div>
) : views.expired.active && (
<div className="without-products">
<IonImg src={withoutCouponsIcon} />
<div className="message">Sin cupones.</div>
</div>
)}
{(lengthUsed > 0 && segmentCoupons === 1 && !loadingCoupons) ? (
<div className="wrapper-orders">
{Object.keys(groupedUsed).map((key: any) => {
return this.renderCouponList(key, groupedUsed[key])
})}
</div>
) : views.used.active && (
<div className="without-products">
<IonImg src={withoutCouponsIcon} />
<div className="message">Sin cupones.</div>
</div>
)}
</IonContent>
)}
{alertDelete && (
<IonAlert
isOpen={true}
message={messageDelete}
buttons={
[{
text: 'Cerrar',
handler: () => this.setState({
alert:false
}),
}]
}
/>
) }
</IonContent>
</IonPage>
)
}
}
export default withRouter(withIonLifeCycle(Coupons))
There are a lot of async functions without error handling in your code, otherwise it looks good.
Therefore I'd guess that there is probably an error thrown somewhere before setState is called which is lost due to the nature of async functions. Async functions will always return a Promise which is rejected with the error in case an unhandled error is thrown.
Therefore you should have at least error handling (in case of async/await usually a try ... catch block) for every react lifecycle function, otherwise the error might get lost. The async function will just return a promise which is rejected with the error and react wont care about the return value. Therefore you wont notice the error.
You can find more on async lifecycle functions here: https://www.valentinog.com/blog/await-react/

BotFramework-WebChat v4: post activity to direct line from the UI to the bot

My WebChat code is based on the React minimizable-web-chat v4.
I want to send a message to the bot when user click the location button.
handleLocationButtonClick function is called and it sends latitude and longitude to the bot.
This is my code:
import React from 'react';
import { createStore, createStyleSet } from 'botframework-webchat';
import WebChat from './WebChat';
import './fabric-icons-inline.css';
import './MinimizableWebChat.css';
export default class extends React.Component{
constructor(props) {
super(props);
this.handleFetchToken = this.handleFetchToken.bind(this);
this.handleMaximizeButtonClick = this.handleMaximizeButtonClick.bind(this);
this.handleMinimizeButtonClick = this.handleMinimizeButtonClick.bind(this);
this.handleSwitchButtonClick = this.handleSwitchButtonClick.bind(this);
this.handleLocationButtonClick = this.handleLocationButtonClick.bind(this);
const store = createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
}
});
}
else if(action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'){
if (action.payload.activity.name === 'locationRequest') {
this.setState(() => ({
locationRequested: true
}));
}
}
return next(action);
});
this.state = {
minimized: true,
newMessage: false,
locationRequested:false,
side: 'right',
store,
styleSet: createStyleSet({
backgroundColor: 'Transparent'
}),
token: 'token'
};
}
async handleFetchToken() {
if (!this.state.token) {
const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();
this.setState(() => ({ token }));
}
}
handleMaximizeButtonClick() {
this.setState(() => ({
minimized: false,
newMessage: false
}));
}
handleMinimizeButtonClick() {
this.setState(() => ({
minimized: true,
newMessage: false
}));
}
handleSwitchButtonClick() {
this.setState(({ side }) => ({
side: side === 'left' ? 'right' : 'left'
}));
}
handleLocationButtonClick(){
var x = document.getElementById("display");
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
this.setState(() => ({
locationRequested: false
}));
}
else
{
x.innerHTML = "Geolocation API is not supported by this browser.";
}
function showPosition(position) {
x.innerHTML = "Latitude: " + position.coords.latitude + "<br>Longitude: " + position.coords.longitude;
this.store.dispatch({
type: 'WEB_CHAT/SEND_MESSAGE',
payload: { text: 'latitude:'+position.coords.latitude+'longitude:'+position.coords.longitude }
});
}
}
render() {
const { state: {
minimized,
newMessage,
locationRequested,
side,
store,
styleSet,
token
} } = this;
return (
<div className="minimizable-web-chat">
{
minimized ?
<button
className="maximize"
onClick={ this.handleMaximizeButtonClick }
>
<span className={ token ? 'ms-Icon ms-Icon--MessageFill' : 'ms-Icon ms-Icon--Message' } />
{
newMessage &&
<span className="ms-Icon ms-Icon--CircleShapeSolid red-dot" />
}
</button>
:
<div
className={ side === 'left' ? 'chat-box left' : 'chat-box right' }
>
<header>
<div className="filler" />
<button
className="switch"
onClick={ this.handleSwitchButtonClick }
>
<span className="ms-Icon ms-Icon--Switch" />
</button>
<button
className="minimize"
onClick={ this.handleMinimizeButtonClick }
>
<span className="ms-Icon ms-Icon--ChromeMinimize" />
</button>
</header>
<WebChat
className="react-web-chat"
onFetchToken={ this.handleFetchToken }
store={ store }
styleSet={ styleSet }
token={ token }
/>
{
locationRequested ?
<div>
<p id="display"></p>
<button onClick={this.handleLocationButtonClick}>
Gélolocation
</button>
</div>
:
<div></div>
}
</div>
}
</div>
);
}
}
When I click the button, I have this error:
And in the console:
What is wrong ??
First, there have been some updates to the a.minimizable-web-chat sample that are worth looking over. Refer to the code, however, as the README.md file has not been fully updated to reflect the changes.
As for your question, try the following changes. When tested, it works successfully for me. Change the component to a function and define store via useMem0().
import React, { useCallback, useMemo, useState } from 'react';
const MinimizableWebChat = () => {
const store = useMemo(
() =>
createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language
}
}
});
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
if (action.payload.activity.from.role === 'bot') {
setNewMessage(true);
}
}
return next(action);
}),
[]
);
[...]
return (
[...]
<WebChat
[...]
store={store}
);
}
export default MinimizableWebChat;
Given the changes implemented in this file, it likely will impact how the other files function with it. My recommendation would be to do a wholesale update to bring your project in line with the current sample. It's really just this file, WebChat.js, and possibly App.js. There are supporting CSS files and the like that can be downloaded, if you don't have them.
Hope of help!

Function only gets called once in NuxtJs

I am using NuxtJs in my project, I a have list of checkboxes, on click of each checkbox I am sending an array of checkboxes to a my POST api which return data.
Here, when I check the first checkbox it returns the data. But when I check the second checkbox it does not does return the data.
I mean it only returns the data on single checkbox checked.
Its working with normal vuejs but not in nuxtjs
My Code:
<script>
import axios from "axios";
import uniq from "lodash/uniq";
export default {
async asyncData({ req, params }) {
let [storeInfo, feedsInfo] = await Promise.all([
axios.get(
process.env.apiURL +
"/stores/findOne?filter[where][store_name]" +
"=" +
params.id
),
axios.post(process.env.apiURL + "feeds/feedsByStores", {
stores: [params.id]
})
]);
return {
stores: storeInfo.data,
feeds: feedsInfo.data,
categories: uniq(feedsInfo.data.map(p => p.feed_category))
};
},
data() {
return {
checkedCategories: [],
checkedCategory: false,
selectedCategories: []
};
},
methods: {
feedsByCategories: function(categories) {
console.log(categories);
axios.post(process.env.apiURL + "feeds/feedsByCategories", {
categories: [categories]
}).then((res) => {
console.log(res);
})
},
categoryChecked: function(category, checked) {
this.display = "inline";
if (checked) {
this.selectedCategories.push(category);
console.log(this.selectedCategories);
this.feedsByCategories(this.selectedCategories);
} else if (!checked) {
const index = this.selectedCategories.indexOf(category);
this.selectedCategories.splice(index, 1);
this.feedsByCategories(this.selectedCategories);
if (this.selectedCategories == "") {
this.display = "none";
this.getFeeds();
}
}
if (!checked && this.selectedCategories.length === 0) {
this.getFeeds();
}
},
uncheckCategory: function(checkedCategory) {
this.checkedCategories = this.checkedCategories.filter(
name => name !== checkedCategory
);
const index = this.selectedCategories.indexOf(checkedCategory);
this.selectedCategories.splice(index, 1);
this.feedsByCategories(this.selectedCategories);
if (this.checkedCategories == "") {
this.display = "none";
this.getFeeds();
}
},
uncheckallCategories: function(event) {
this.checkedCategories = [];
this.display = "none";
this.search = "";
this.Search = "";
this.filteredCategories;
},
getFeeds() {
return this.feeds;
}
}
};
</script>
<template>
<v-layout>
<ul class="list-unstyled scrollbar">
<li v-for="(feedcategory, index) in categories" :key="feedcategory.id">
<input type="checkbox" name="category" #change="categoryChecked(feedcategory,$event.target.checked)"
:id="index + 1" :value="feedcategory" v-model="checkedCategories">
{{ feedcategory }}
</li>
</ul>
</v-layout>
</template>
My Typo,
I removed the brackets for my categories array and it worked:
feedsByCategories: function(categories) {
console.log(categories);
axios.post(process.env.apiURL + "feeds/feedsByCategories", {
categories: categories
}).then((res) => {
console.log(res);
})
}