Access array value in promise - vue.js

I have emited value from the child and returning the value in a parent, but when I'm trying to access it prints me just blank array?
I haven't dealt with promises in the past, so I don't know much about that...I know something about async and await method..but I don't know how to bundle this function into this.
Thanks in advance for your help.
This is what I have...
insertMessage.vue
export default {
data(){
return{
message:'',
isLoading:false,
}
},
methods:{
addMessage(){
if(this.message !=''){
this.sendData();
} else{
}
},
sendData(){
this.isLoading = true;
this.$http.post('/add-message' , {message:this.message, id_rooms:this.$route.params.id_rooms}).then((response) => {
console.log(response.json());
if(response.body != 'Error'){
this.message = '';
this.$root.$emit('new_message', response.json());
} else{
this.isLoading = false;
}
}, (response) =>{
this.isLoading = false;
});
}
}
}
</script>
print.vue
<template>
<div class="msg_history">
<get_message :messages="messages"></get_message>
<add_message></add_message>
</div>
</template>
<script>
import addMessage from './add-message';
import getMessage from './get-messages';
export default {
components: {
get_message: getMessage,
add_message: addMessage,
},
data() {
return {
messages: []
}
},
mounted(){
this.$root.$on('new_message', (data) => {
this.messages.push(data);
});
},
}
</script>
fetchMessages.vue
<template>
<div class="incoming_msg">
<div class="received_msg">
<div v-for="message in messages" class="received_withd_msg">
{{message.created}} //This is what I want
{{message}} //This is working but print just { "promise": {} }
</div>
</div>
</div>
</template>
<script>
export default {
props:['messages'],
data(){
return{
}
}
}
</script>

You can just do emit in then:
this.$http.post('/add-message' , {message:this.message, id_rooms:this.$route.params.id_rooms}).then((response) => {
if(response.body != 'Error'){
response.json().then( json => {
this.message = '';
console.log(json);
this.$root.$emit('new_message', json);
});
} else {
this.isLoading = false;
}
}, (response) => {
this.isLoading = false;
});

Related

How to forceUpdate sibling component in VueJS

I have created a component which have two child components AddContactList and ViewContactLists, Here I need to forceUpdate ViewContactList when new entry inserted from AddContactList component
This is AddContactList components script
<script>
export default {
data() {
return {
fields: {},
errors: {},
success: false,
loaded: true,
}
},
methods: {
submit() {
if (this.loaded) {
this.loaded = false;
this.success = false;
this.errors = {};
console.log('Loading..');
axios.post('/submit', this.fields).then(response => {
this.fields = {}; //Clear input fields.
this.loaded = true;
this.success = true;
console.log('done..');
// --Here I need to update ViewContactList component
}).catch(error => {
this.loaded = true;
if (error.response.status === 422) {
console.log(error.response.data.errors)
this.errors = error.response.data.errors || {};
}
});
}
},
},
}
</script>
This is ViewContactList components script
<script>
import pagination from 'laravel-vue-pagination'
export default {
name:"ContactList",
components:{
pagination
},
data(){
return {
ContactList:{
type:Object,
default:null
}
}
},
mounted(){
this.list()
},
methods:{
async list(page=1){
await axios.get(`/getContactLists?page=${page}`).then(({data})=>{
this.ContactList = data
}).catch(({ response })=>{
console.error(response)
})
}
}
}
</script>
You can simply achieve this by emitting an event on successful save to the parent and then from parent component you can invoke the contact list component method with the help of ref.
Live demo :
Vue.component('childone', {
props: ['childmsg', 'childOneRef'],
template: `<p>{{ childmsg }} <button #click="$emit('savesuccess')">Add Contact</button></p>`
});
Vue.component('childtwo', {
props: ['childmsg', 'childoneref'],
template: '<p>{{ childmsg }}</p>',
methods: {
getupdtedList() {
console.log('Contact List call');
}
}
});
var app = new Vue({
el: '#app',
methods: {
callViewContactListCompMethod() {
this.$refs.contactListRef.getupdtedList();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<childone #savesuccess="callViewContactListCompMethod" childmsg="This is a child 1 message">
</childone>
<childtwo ref="contactListRef" childmsg="This is a child 2 message">
</childtwo>
</div>
If your parent component is ContactComponent
ContactComponent
|--AddContactList
|--ViewContactList
When you insert contact in add contact list emit the #addList event to the parent component.
Then pass the contact list as a props to ViewContactList.
when the props is changed, the ViewContactList component will be re-rendered automatically.

Vuejs & Auth0 : I need to reload page to be Authenticated

I'm a beginner in Vue, and I implemented Auth0 to my Web App using Vue3.
My issue: after logging in, my API call to retrieve data get an unauthorized error 403. If I reload the page, everything is working fine.
What should I do to avoid reloading the page to get authenticated directly?
Here are my scripts:
Main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './index.css'
import dayjs from 'dayjs'
import Datepicker from 'vue3-date-time-picker'
import 'vue3-date-time-picker/dist/main.css'
import { setupAuth } from './auth/index.js'
import authConfig from './auth/config.js'
function callbackRedirect(appState) {
router.push(appState && appState.targetUrl ? appState.targetUrl : '/' );
}
setupAuth(authConfig, callbackRedirect).then((auth) => {
let app = createApp(App).use(router);
app.config.globalProperties.$dayjs = dayjs;
app.component('Datepicker', Datepicker);
app.use(auth).mount('#app');
})
My App.vue script:
<template>
<div v-if="isAuthenticated">
<NavBar />
<router-view/>
</div>
</template>
<script>
import NavBar from './components/NavBar.vue'
export default {
components: { NavBar },
data(){
return {
isAuthenticated: false,
}
},
async mounted(){
await this.getAccessToken()
},
methods: {
async getAccessToken(){
try {
const accessToken = await this.$auth.getTokenSilently()
localStorage.setItem('accessToken', accessToken)
this.isAuthenticated = true
} catch (error) {
console.log('Error occured while trying to retrieve Access Token...', error)
}
},
},
}
</script>
and my Home.vue loading the data:
<template>
<div class="home">
<div class="py-10">
<header>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold leading-tight text-gray-900">Monitoring Dashboard</h1>
</div>
</header>
<main>
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<h3 class="m-5 text-lg leading-6 font-medium text-gray-900">Main KPIs</h3>
<div class="md:grid md:grid-cols-3 md:gap-6">
<div v-for="(item, index) in stats" :key="index" class="md:col-span-1">
<div class="bg-white p-5 border-gray-50 rounded-lg shadow-lg mb-5">
<span class="text-sm font-medium text-gray-500 truncate">{{ item.name }}</span>
<p class="mt-1 text-3xl font-bold text-gray-900">{{ parseFloat(item.stat.toFixed(2)) }}</p>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import ProductsService from '../services/products.service'
export default {
name: 'Home',
data(){
return{
user: '',
products: '',
stats: '',
}
},
async mounted(){
await this.readProducts()
await this.buildStats()
},
methods: {
async readProducts(){
let temp = null
try {
temp = await ProductsService.readProducts()
this.products = temp.data
} catch (error) {
console.log('Error: cannot retrieve all products...')
}
},
async buildStats(){
//Nb products
const nbProducts = this.products.length
//Nb offers & Uniq NbRetailers
let nbOffers = 0
let retailers = []
for(let product of this.products){
for(let offer of product.offers){
retailers.push(offer.retailer)
nbOffers += 1
}
}
const nbRetailers = _.uniq(retailers).length
this.stats = [
{ name: 'Number of Retailers', stat: nbRetailers },
{ name: 'Number of Products', stat: nbProducts },
{ name: 'Number of Offers', stat: nbOffers },
]
},
},
watch: {
products: function(){
this.buildStats()
}
}
}
</script>
My ./auth/index.js file:
import createAuth0Client from '#auth0/auth0-spa-js'
import { computed, reactive, watchEffect } from 'vue'
let client
const state = reactive({
loading: true,
isAuthenticated: false,
user: {},
popupOpen: false,
error: null,
})
async function loginWithPopup() {
state.popupOpen = true
try {
await client.loginWithPopup(0)
} catch (e) {
console.error(e)
} finally {
state.popupOpen = false
}
state.user = await client.getUser()
state.isAuthenticated = true
}
async function handleRedirectCallback() {
state.loading = true
try {
await client.handleRedirectCallback()
state.user = await client.getUser()
state.isAuthenticated = true
} catch (e) {
state.error = e
} finally {
state.loading = false
}
}
function loginWithRedirect(o) {
return client.loginWithRedirect(o)
}
function getIdTokenClaims(o) {
return client.getIdTokenClaims(o)
}
function getTokenSilently(o) {
return client.getTokenSilently(o)
}
function getTokenWithPopup(o) {
return client.getTokenWithPopup(o)
}
function logout(o) {
return client.logout(o)
}
export const authPlugin = {
isAuthenticated: computed(() => state.isAuthenticated),
loading: computed(() => state.loading),
user: computed(() => state.user),
getIdTokenClaims,
getTokenSilently,
getTokenWithPopup,
handleRedirectCallback,
loginWithRedirect,
loginWithPopup,
logout,
}
export const routeGuard = (to, from, next) => {
const { isAuthenticated, loading, loginWithRedirect } = authPlugin
const verify = () => {
// If the user is authenticated, continue with the route
if (isAuthenticated.value) {
return next()
}
// Otherwise, log in
loginWithRedirect({ appState: { targetUrl: to.fullPath } })
}
// If loading has already finished, check our auth state using `fn()`
if (!loading.value) {
return verify()
}
// Watch for the loading property to change before we check isAuthenticated
watchEffect(() => {
if (loading.value === false) {
return verify()
}
})
}
export const setupAuth = async (options, callbackRedirect) => {
client = await createAuth0Client({
...options,
})
try {
// If the user is returning to the app after authentication
if (
window.location.search.includes('code=') &&
window.location.search.includes('state=')
) {
// handle the redirect and retrieve tokens
const { appState } = await client.handleRedirectCallback()
// Notify subscribers that the redirect callback has happened, passing the appState
// (useful for retrieving any pre-authentication state)
callbackRedirect(appState)
}
} catch (e) {
state.error = e
} finally {
// Initialize our internal authentication state
state.isAuthenticated = await client.isAuthenticated()
state.user = await client.getUser()
state.loading = false
}
return {
install: (app) => {
app.config.globalProperties.$auth = authPlugin
},
}
}

Vue Laravel Pusher. How to leave a channel

OK this has been posted a few times and while each of the solutions seem similar, none of them work for me. So lets start with the code.
Main.vue
<template>
...
<room-tabs :selectedRoomId="currentRoom.id" :rooms="joinedRooms"
#onSelectRoom="currentRoom = $event" #onLeaveRoom="leaveRoom($event)"/>
...
</template>
methods: {
connect() {
if (this.currentRoom?.id) {
let vm = this;
this.getMessages();
Echo.join("chat." + this.currentRoom.id)
.here(users => {
this.sendUserActivityNotification(this.$page.props.user, true)
this.users = users
this.usersCount = users.length
})
.joining(user => {
this.users.push(user)
this.usersCount = this.usersCount+1
})
.leaving(user => {
this.sendUserActivityNotification(user, false)
this.users = this.users.filter(({id}) => (id !== user.id))
this.usersCount = this.usersCount-1
})
.listen('NewChatMessage', (e) => {
vm.getMessages();
});
}
},
leaveRoom({id}) {
Echo.leave("chat." + this.currentRoom.id)
this.$store.splice(RoomTypes.LEAVE_ROOM, id)
}
},
RoomTabs.vue
<template>
...
<XIcon class="float-right h-3.5 w-3.5 -mt-2 -mr-2 text-black" aria-hidden="true" #click="leaveRoom(selectedRoomId)"/>
...
</template>
<script>
import {XIcon} from '#heroicons/vue/solid'
import {types as RoomTypes} from "../../../Store/modules/rooms/rooms.types";
export default {
components: {
XIcon,
},
props: ['rooms', 'selectedRoomId'],
emits: ['onSelectRoom', 'onLeaveRoom', 'onClose'],
methods: {
leaveRoom(id) {
this.$store.splice(RoomTypes.LEAVE_ROOM, id)
this.$emit('onClose');
}
},
}
</script>
I cannot seem to disconnect from the room when I click the X on the room tab. What am I missing here?

Migrating "detect click outside" custom directive from Vue 2 to Vue 3

Based on this question Detect click outside element and this answer https://stackoverflow.com/a/42389266, I'm trying to migrate the directive from Vue 2 to Vue 3. It seems that binding.expression and vnode.context not exists more. How can I make it work?
app.directive('click-outside', {
beforeMount (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
if (!(el === event.target || el.contains(event.target))) {
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unmounted (el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
}
});
You can use binding.value instead like this:
const { createApp } = Vue;
const highlightEl = (color ) => (event, el) => {
if (el) {
el.style.background = color;
} else {
event.target.style.background = color;
}
}
const clearHighlightEl = (event, el) => {
if (el) {
el.style.background = '';
} else {
event.target.style.background = '';
}
}
const app = Vue.createApp({
setup() {
return {
highlightEl,
clearHighlightEl
}
}
})
app.directive('click-outside', {
mounted(el, binding, vnode) {
el.clickOutsideEvent = function(event) {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event, el);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unmounted(el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
}
});
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app">
<h1 v-click-outside="highlightEl('yellow')" #click="clearHighlightEl">Element 1</h1>
<p v-click-outside="highlightEl('#FFCC77')" #click="clearHighlightEl">Element 2</p>
</div>
out of the context, there's an easier way in vue3 with composition.
Link to Vueuse ClickOutside (Vue 3)
Link to Vueuse ClickOutside(Vue 2)
<template>
<div ref="target">
Hello world
</div>
<div>
Outside element
</div>
</template>
<script>
import { ref } from 'vue'
import { onClickOutside } from '#vueuse/core'
export default {
setup() {
const target = ref(null)
onClickOutside(target, (event) => console.log(event))
return { target }
}
}
</script>
you can use ref to find out if the element contains the element clicked
<template>
<div ref="myref">
Hello world
</div>
<div>
Outside element
</div>
</template>
<script>
export default {
data() {
return {
show=false
}
},
mounted(){
let self = this;
document.addEventListener('click', (e)=> {
if (self.$refs.myref !==undefined && self.$refs.myref.contains(e.target)===false) {
//click outside!
self.show = false;
}
})
}
}
</script>
vue2 solution:
<script>
export default {
name: 'onClickOutside',
props: ['clickOutside'],
mounted() {
const listener = e => {
if (e.target === this.$el || this.$el.contains(e.target)) {
return
}
this.clickOutside()
}
document.addEventListener('click', listener)
this.$once('hook:beforeDestroy', () => document.removeEventListener('click', listener))
},
render() {
return this.$slots.default[0]
},
}
</script>
vue3:
<script>
import { getCurrentInstance, onMounted, onBeforeUnmount, ref, defineComponent } from 'vue'
export default defineComponent({
name: 'OnClickOutside',
props: ['clickOutside'],
setup(props, { emit, attrs, slots }) {
const vm = getCurrentInstance()
const listener = event => {
const isClickInside = vm.subTree.children.some(element => {
const el = element.el
return event.target === el || el.contains(event.target)
})
if (isClickInside) {
console.log('clickInside')
return
}
props.clickOutside && props.clickOutside()
}
onMounted(() => {
document.addEventListener('click', listener)
})
onBeforeUnmount(() => {
document.removeEventListener('click', listener)
})
return () => slots.default()
},
})
</script>

Variable not updated after vuex mutation

I am creating a settings page, where I fetch some data from the API and I am using Vuex to handle mutations.
I can see that the Vuex completes properly, but value for my dailyCount variable doesn't update in frontend.
This is my Settings component:
<template>
<div>
<div class="row col">
<h1>Settings</h1>
</div>
<div class="row col">
<div class="well">
<form class="form-inline">
<input type="number" v-model="dailyCount" />
{{ dailyCount }}
</form>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'settings',
data () {
return {
dailyCount: 500
};
},
created () {
this.$store.dispatch('settings/fetchSetting');
},
computed: {
isLoading() {
return this.$store.getters['user/isLoading'];
},
hasError() {
return this.$store.getters['user/hasError'];
},
error() {
return this.$store.getters['user/error'];
},
},
}
</script>
I do mutations here:
import SettingsAPI from '../api/settings';
export default {
namespaced: true,
state: {
isLoading: false,
error: null,
settings: null,
},
getters: {
isLoading (state) {
return state.isLoading;
},
hasError (state) {
return state.error !== null;
},
error (state) {
return state.error;
},
user (state) {
return state.user;
},
},
mutations: {
['FETCHING_SETTINGS'](state) {
state.isLoading = true;
state.error = null;
state.settings = null;
},
['FETCHING_SETTINGS_SUCCESS'](state, settings) {
state.isLoading = false;
state.error = null;
state.settings = settings;
},
['FETCHING_SETTINGS_ERROR'](state, error) {
state.isLoading = false;
state.error = error;
state.settings = null;
},
},
actions: {
fetchSetting ({commit}) {
commit('FETCHING_SETTINGS');
return SettingsAPI.get()
.then(res => {commit('FETCHING_SETTINGS_SUCCESS', res.data);})
.catch(err => commit('FETCHING_SETTINGS_ERROR', err));
},
},
}
And call to a server is done here (api/settings.js - it is imported in mutation file):
import axios from 'axios';
export default {
get() {
return axios.get('/user');
},
}
Can you see what am I doing wrong? I am trying to debug it using Vuejs debug toolbar, but all seems to work fine.
You need to get store state from vuex and inject to Vue component, either by this.$store.state or this.$store.getters.
For example:
<script>
export default {
name: 'settings',
data () {
return {
dailyCount: 500
};
},
created () {
this.$store.dispatch('settings/fetchSetting');
},
computed: {
isLoading() {
return this.$store.getters['user/isLoading'];
},
hasError() {
return this.$store.getters['user/hasError'];
},
error() {
return this.$store.getters['user/error'];
},
settings() {
return this.$store.state.settings
}
},
watch: {
settings () {
this.dailyCount = this.settings.dailyCount
}
}
}
</script>