ERROR in conditions to show a loading bar - react-native
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/
Related
rtk query show when internet is off?
I use rtk query. if I close my internet connection and navigate to a screen I see no Loader and or something else I see nothing. So when I turn on my internet connection then nothing happens. How can I say to the user when the internet is offline that I send him a message and when Internet is on that the rtk query automatically refetch the data ? Cart.tsx const ShoppingCart: React.FC<Props> = ({}) => { const user_id = useSelector((state: RootState) => state.Auth.user.user_id); const { data, isFetching, isLoading } = useCartQuery({user_id}); if(isLoading) { return ( <Loader /> ) } else if(data && data.length > 0) { return ( <ShoppingCartComponent products={data} /> ) } else if(data && data.length === 0) { return ( <ShoppingCartEmpty /> ) } } export default ShoppingCart; Cart.ts API reducerPath: 'ShoppingCartApi', baseQuery: fetchBaseQuery({ baseUrl: `${API_ENDPOINT}/` }), tagTypes: ['CART'], endpoints: (builder) => ({ cart: builder.query<IProduct[], { user_id?: number; unlogged_uuid?: string; }>({ query: (data) => ({ url: '/cart', method: 'POST', body: data }), providesTags: ['CART'] }),
You can use the navigator.onLine to check if the user's device is currently online. Then listen for the online event, which is fired when the device's internet connection is restored and refetch data. const ShoppingCart: React.FC<Props> = ({}) => { const user_id = useSelector((state: RootState) => state.Auth.user.user_id); const { data, isFetching, isLoading, refetch } = useCartQuery({user_id}); useEffect(() => { const handleOnlineEvent = () => { // Refetch data from the API when the online event is fired refetch(); }; window.addEventListener('online', handleOnlineEvent); return () => { window.removeEventListener('online', handleOnlineEvent); }; }, []); if (!navigator.onLine) { return ( <div> <p>You are currently offline. Please check your internet connection and try again.</p> </div> ); } if (isLoading) { return ( <Loader /> ) } else if(data && data.length > 0) { return ( <ShoppingCartComponent products={data} /> ) } else if(data && data.length === 0) { return ( <ShoppingCartEmpty /> ) } }
How to write testcase of $bvModal.msgBoxConfirm of vuebootstrap in nuxt.js
How to write a test case for this component, how to write a unit case for this, attach to dom might now working in this case, How to write a test case for this component, how to write a unit case for this, attach to dom might now working in this case, <template> <div> <div class="mb-2"> <b-button #click="showMsgBoxOne">Simple msgBoxConfirm</b-button> Return value: {{ String(boxOne) }} </div> <div class="mb-1"> <b-button #click="showMsgBoxTwo">msgBoxConfirm with options</b-button> Return value: {{ String(boxTwo) }} </div> </div> </template> <script> export default { data() { return { boxOne: '', boxTwo: '' } }, methods: { showMsgBoxOne() { this.boxOne = '' this.$bvModal.msgBoxConfirm('Are you sure?') .then(value => { this.boxOne = value }) .catch(err => { // An error occurred }) }, showMsgBoxTwo() { this.boxTwo = '' this.$bvModal.msgBoxConfirm('Please confirm that you want to delete everything.', { title: 'Please Confirm', size: 'sm', buttonSize: 'sm', okVariant: 'danger', okTitle: 'YES', cancelTitle: 'NO', footerClass: 'p-2', hideHeaderClose: false, centered: true }) .then(value => { this.boxTwo = value }) .catch(err => { // An error occurred }) } } } </script>
async showMsgBoxTwo() { this.boxTwo = '' try { const res = await this.$bvModal.msgBoxConfirm('Please confirm that you want to delete everything.', { title: 'Please Confirm', size: 'sm', buttonSize: 'sm', okVariant: 'danger', okTitle: 'YES', cancelTitle: 'NO', footerClass: 'p-2', hideHeaderClose: false, centered: true }) this.boxTwo = res } catch (e) { // error } } then it('test', () => { const wrapper = shallowMount(Component, { store, localVue, propsData: {}, }) const spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') spy.mockImplementation(() => Promise.resolve(some value)) wrapper.vm.showMsgBoxTwo() wrapper.vm.$nextTick(() => { expect(spy).toHaveBeenCalled() }) })
Quasar upload file from axios
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
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!
How do I combine these two functions into a single function that takes two parameters?
I have two functions: setCallModalFalse = (incomingFlag) => () => { if (incomingFlag === 'EmergencyCall') { this.setState({ callModal: false }); this.props.navigation.navigate('EmergencyCall'); } else { loggingService.debug('we are not going anywhere'); this.setState({ callModal: false }); } } setBeepModalFalse = (incomingFlag) => () => { if (incomingFlag === 'PoliceIconPage') { this.setState({ beepModal: false }); this.props.navigation.navigate('PoliceIconPage'); } else { loggingService.debug('hey you pressed setbeepmodalfalse'); this.setState({ beepModal: false }); } I'm trying to combine them into a single function handleModalAndNavigation = (whichModal, incomingFlag) => () => { if (whichModal === 'beep' && incomingFlag === 'PoliceIconPage') { this.setState({ beepModal: false }); this.props.navigation.navigate('PoliceIconPage'); } else { this.setState({ beepModal: false }); } if (whichModal === 'call' && incomingFlag === 'EmergencyCall') { this.setState({ callModal: false }); this.props.navigation.navigate('EmergencyCall'); } else { this.setState({ callModal: false }); } } my constructor looks like this: constructor(props) { super(props); this.setBeepModalFalse = this.setBeepModalFalse.bind(this); this.setCallModalFalse = this.setCallModalFalse.bind(this); this.callButton = this.callButton.bind(this); this.alarmButton = this.alarmButton.bind(this); this.handleModalAndNavigation = this.handleModalAndNavigation.bind(this); this.state = { callModal: false, beepModal: false, }; this.batteryLevelIndicator = ''; } However, when I try to load this, I get the error "TypeError: Cannot read property 'bind' of undefined'" There aren't any issues when I use setCallModalFalse and setBeepModalFalse. Why is my function suddenly undefined?
You need to remove bind function declaration //this.handleModalAndNavigation = this.handleModalAndNavigation.bind(this); in constructor because you have already use ES6 syntax while declaring functions. and your function declaration should be something look like this, handleModalAndNavigation = (whichModal, incomingFlag) => { if (whichModal === 'beep' && incomingFlag === 'PoliceIconPage') { this.setState({ beepModal: false }); this.props.navigation.navigate('PoliceIconPage'); } else { this.setState({ beepModal: false }); } if (whichModal === 'call' && incomingFlag === 'EmergencyCall') { this.setState({ callModal: false }); this.props.navigation.navigate('EmergencyCall'); } else { this.setState({ callModal: false }); } }