Passing variables from within VueJS components - javascript

I am new to VueJs and Laravel. I am trying to build a chat app following this tutorial, and my problem is that I am not able to pass a CONTACT variable (object type) from ChatApp.vue to Conversation.vue. The console gives this error
[Vue warn]: Invalid prop: type check failed for prop "contact". Expected Object, got Null
found in ---> <MessageFeed> at resources/js/officer/components/MessageFeed.vue
<Conversation> at resources/js/officer/components/Conversation.vue
<ChatApp> at resources/js/officer/components/ChatApp.vue
<Root>
first the variable has to pass through ChatApp to Conversation and then to MessageFeed. I tried printing it on console, in ChatApp.vue, it printed the variable's property i.e. name but when I try to print it in Conversation.vue, it says it is null and also gives the above error.
please see the code below:
ChatApp.vue
<template>
<div class="chat-app">
<Conversation :contact="selectedContact" :messages="messages" #new="saveNewMessage"/>
<ContactsList :contacts="contacts" #selected="startConversationWith"/>
</div>
</template>
<script>
import Conversation from "./Conversation";
import ContactsList from "./ContactsList";
const axios = require('axios');
export default {
props: {
user: {
type: Object,
required: true
}
},
data() {
return {
selectedContact: null,
messages: [],
contacts: []
}
},
mounted() {
Echo.private(`messages.${this.user.id}`)
.listen('NewMessage', (e) => {
this.handleIncoming(e.message);
});
console.log(this.user);
axios.get('/officer/contacts')
.then((response) => {
this.contacts = response.data;
console.log("chatapp1: " + this.contacts[0].name);
})
.catch((error) => {
// handle error
console.log(error);
});
},
methods: {
startConversationWith(contact) {
axios.get(`/officer/conversation/${contact.id}`)
.then((response) => {
this.message = response.data;
this.selectedContact = contact;
console.log("chatapp: " + this.selectedContact.name);
});
},
saveNewMessage(message) {
this.messages.push(message);
},
handleIncoming(message) {
if (this.selectedContact && message.from === this.selectedContact.id) {
this.saveNewMessage(message);
return;
}
this.updateUnreadCount(message.from_contact, false);
},
updateUnreadCount(contact, reset) {
this.contacts = this.contacts.map((single) => {
if (single.id !== contact.id) {
return single;
}
if (reset)
single.unread = 0;
else
single.unread += 1;
return single;
})
}
},
components: {Conversation, ContactsList}
}
Conversation.vue
<template>
<div class="conversation">
<h1>{{ contact ? contact.name : 'Select a Contact' }}</h1>
<MessagesFeed :contact="contact" :messages="messages"/>
<MessageComposer #send="sendMessage"/>
</div>
</template>
<script>
import MessagesFeed from './MessageFeed';
import MessageComposer from './MessageComposer';
export default {
props: {
contact: {
type: Object,
default: null
},
messages: {
type: Array,
default: []
}
},
mounted() {
console.log("conversation: "+this.contact);
},
methods: {
sendMessage(text) {
if (!this.contact) {
return;
}
console.log(text);
axios.post('/conversation/send', {
contact_id: this.contact.id,
text: text
}).then((response) => {
this.$emit('new', response.data);
})
}
},
components: {MessagesFeed, MessageComposer}
}
MessageFeed.vue
<template>
<div class="feed" ref="feed">
<ul v-if="contact">
<li v-for="message in messages" :class="`message${message.to == contact.id ? 'sent' : 'received'}`"
:key="message.id">
<div class="text">
{{message.text}}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
contact: {
type: Object,
required: true
},
messages: {
type: Array,
required: true
}
},
methods: {
scrollToBottom() {
setTimeout(() => {
this.$refs.feed.scrollTop = this.$refs.feed.scrollHeight - this.$refs.feed.clientHeight;
}, 1);
}
},
watch: {
contact(contact) {
this.scrollToBottom();
},
messages(messages) {
this.scrollToBottom();
}
}
}
please, let me know what am I missing here.

Because you konfigurated it to be an Object but you declared it on the initial state as null
selectedContact: null
That should be an object
It should look something like this i guess:
selectedContact: {
id:0
}

I fixed the issue.
This was due to axios.get() response thing. It was fetching data from database after a couple of minutes and the Conversation.vue and ContactList.vue were mounting immediately so they were not getting data.
With the help of my friend, it was fixed by loading the component only when axios have got all the contactlist from database and the very first contact have been passed to the Conversation.vue component i.e using v-if property of component.

Related

GET json api data in datatable with axios and vuejs

I have a datatable and I want to pass the data according to the api that returns a json using findAll() from the sequelize..
But in console.log when I call the getUser method it returns the data with the data. But when you insert data into the datatable: it is informing you that it has no data.
Example datatable using in code: https://vuejsexamples.com/a-vue-plugin-that-adds-advanced-features-to-an-html-table/
<template>
<div>
<data-table v-bind="bindings"/>
</div>
</template>
<script>
import ActionButtons from "../Components/ActionButtons"
import axios from "axios"
export default {
name: 'Usuarios',
data(){
return {
user: this.user,
errors: []
}
},
computed: {
bindings() {
return {
data: this.user,
lang: "pt-br",
actionMode: "single",
columns: [
{
key:"code",
title:"Código"
},
{
key:"name",
title:"Nome"
},
{
key:"login",
title:"Login"
},
{
key:"cpf",
title:"CPF"
},
{
key:"actions",
title:"Ações",
component: ActionButtons,
},
],
}
}
},
methods:{
getUser() {
axios
.get("http://localhost:3005/users")
.then((res) => {
this.user = res.data;
})
.catch((error) => {
console.log(error);
});
},
}
};
</script>
I believe the reason it doesn't work is because the getUser() method is defined but not called.
If you move the async request into a created() lifecycle hook, the request will be made before the component is mounted, so the table should have access to the data. https://v3.vuejs.org/api/options-lifecycle-hooks.html#created
I think this will really help you.
<template>
<v-card>
<v-card-title>
Liste du Personnel
</v-card-title>
<v-card-text class="mt-3">
<main>
<data-table v-bind="listing_personnel" #actionTriggered="handleAction"/>
<br>
</main>
</v-card-text>
</v-card>
</template>
<script>
import axios from "axios";
import ActionButtons from "./ActionButtons"
export default {
data(){
return {
user: [],
errors: []
}
},
created() {
this.getPersonnel();
},
methods: {
handleAction(actionName, data) {
console.log(actionName, data);
window.alert("check out the console to see the logs");
},
async getPersonnel() {
try {
const response = await axios.get("SeletAllUsers");
this.user = response.data;
}
catch (error) {
console.log(error);
}
},
},
computed: {
listing_personnel() {
return {
data: this.user,
actionMode: "multiple",
columns: [
{
key: "user_matricule",
title: "Matricule"
},
{
key: "user_lastname",
title: "Noms"
},
{
key: "user_firstname",
title: "Prénoms"
},
{
key: "user_contact1",
title: "Contact"
},
{
key: "user_email",
title: "Email"
},
{
key:"actions",
title:"Actions",
component: ActionButtons,
},
]
};
}
},
};
</script>
/* preferably put this in its main.js
axios.defaults.baseURL = 'http://localhost:8080/';*/*

VueJs not displaying image that gets it from an axios promise and then puts image url in src

I'm trying to call an API and once I have the response I want to target the image_url property but I'm getting this error Error in render: "TypeError: Cannot read property 'image_url' of undefined" and Cannot read property 'image_url' of undefined at VueComponent.getImages1 (Info.vue?42ed:39)
I've tested my code in another Javascript file and over there it works but for some reason not here. I've also checked for if the parent state is sending data correctly to child state through Vue console and it has worked.
Info.vue
<template>
<section>
<img :src="getImages1()" alt="./assets/notFound.png" />
<img :src="getImages2()" alt="./assets/notFound.png" />
</section>
</template>
<script>
import axios from "axios";
export default {
props: {
anime1: String,
anime2: String,
},
methods: {
animeFind(anime) {
axios
.get(`https://api.jikan.moe/v3/search/anime?q=${anime}`)
.then(async function(response) {
const id = await response.data["results"][0]["mal_id"];
await axios
.get(`https://api.jikan.moe/v3/anime/${id}`)
.then(function(response) {
return response.data;
})
.catch(function(error) {
return error; // take care of this later
});
})
.catch(function(error) {
return error; // take care of this later
});
},
// eslint-disable-next-line no-unused-vars
getImages1() {
let response = this.animeFind(this.anime1);
return response["image_url"];
},
getImages2() {
let response = this.animeFind(this.anime2);
return response["image_url"];
},
},
};
</script>
<style></style>
I tried doing this and it worked
main.js
const axios = require("axios");
const animeFind = (anime) =>
axios
.get(`https://api.jikan.moe/v3/search/anime?q=${anime}`)
.then(async function (response) {
const id = await response.data["results"][0]["mal_id"];
await axios
.get(`https://api.jikan.moe/v3/anime/${id}`)
.then(function (response) {
console.log(response.data["image_url"]);
})
.catch(function (error) {
console.log(error);
});
})
.catch(function (error) {
console.log(error);
});
animeFind("Naruto");
animeFind("Cowboy Bebop");
This is the parent component, when the button is clicked only then should the image change
<template>
<section class="hero">
<div class="parent-1">
<h1 class="title is-1">Compare two animes! :)</h1>
</div>
<div class="columns">
<div class="column">
<b-field class="label" label="Anime 1">
<b-input value="Enter the first anime!" v-model="anime1"></b-input>
</b-field>
</div>
<div class="column">
<b-field class="label" label="Anime 2">
<b-input value="Enter the second anime!" v-model="anime2"></b-input>
</b-field>
</div>
</div>
<div class="button-spacing">
<b-button class="button" type="is-primary" #click="checkComplete"
>Compare!</b-button
>
</div>
<Info :anime1="anime1" :anime2="anime2" v-if="success">Wow</Info>
</section>
</template>
<script>
import Vue from "vue";
import Buefy from "buefy";
import "buefy/dist/buefy.css";
import Info from "./Info.vue";
Vue.use(Buefy);
export default {
components: {
Info,
},
data() {
return {
anime1: "",
anime2: "",
success: false,
};
},
methods: {
// log() {
// console.log(this.anime1);
// console.log(this.anime2);
// },
checkComplete() {
if (this.anime1.length > 0 && this.anime2.length > 0) {
// let animeData1 = this.animeFind(this.anime1);
// let animeData2 = this.animeFind(this.anime2);
this.success = true;
return this.$buefy.toast.open({
message: "Yay, just a moment now!",
type: "is-success",
position: "is-bottom",
duration: 3000,
});
}
this.success = false;
return this.$buefy.toast.open({
duration: 3000,
message: `Please fill out both fields`,
position: "is-bottom",
type: "is-danger",
});
},
},
};
</script>
I think you're still a little confused with promises. your animFind function is not returning anything.
Instead try
<template>
<section>
<img :src="url1" alt="./assets/notFound.png" />
<img :src="url2" alt="./assets/notFound.png" />
</section>
</template>
<script>
import axios from "axios";
export default {
props: {
anime1: String,
anime2: String,
},
data() {
return {
url1: '',
url2: '',
error: ''
}
},
methods: {
animeFind(anime, data) {
axios
.get(`https://api.jikan.moe/v3/search/anime?q=${anime}`)
.then(response => {
const id = response.data["results"][0]["mal_id"];
axios
.get(`https://api.jikan.moe/v3/anime/${id}`)
.then(response => this[data] = response.data["image_url"]);
})
.catch(error => {
this.error = error; // take care of this later
});
}
},
watch: {
anime1: {
immediate: true,
handler(newVal, oldVal) {
this.animeFind(newVal, 'url1');
},
},
anime2: {
immediate: true,
handler(newVal, oldVal) {
this.animeFind(newVal, 'url2');
},
},
},
};
</script>
Notice the use if arrow functions to stay in the vue scope
The getImages() function return before the animeFind() would return. So the getImages() will return undefined.
You can put the axios call into hooks and when you return the response.data object, you can assign it to a property in the data object. You use this property instead the function in the template, so the component will be reactive.
Notice that you should use regular function on the outer function in the axios call and arrow functions on the then() responses for getting a proper this.
I am taking care of only one image example for simplicity, but editing this is not so complicated.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<section>
<p>Show image here</p>
<img :src="urlResponse['image_url']" alt="./assets/notFound.png">
</section>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
urlResponse: {}
};
},
props: {
anime1: String,
anime2: String
},
created() {
this.animeFind(this.anime1);
},
updated() {
this.animeFind(this.anime1);
},
methods: {
animeFind: function(anime) {
axios
.get(`https://api.jikan.moe/v3/search/anime?q=${anime}`)
.then(async response => {
const id = await response.data["results"][0]["mal_id"];
await axios
.get(`https://api.jikan.moe/v3/anime/${id}`)
.then(response => {
this.urlResponse = Object.assign(
{},
this.urlResponse,
response.data
);
return response.data;
})
.catch(function(error) {
return error; // take care of this later
});
})
.catch(function(error) {
return error; // take care of this later
});
}
}
};
</script>
<style></style>

How to pass data from vue component to view(HTML) in laravel

I'm trying to pass data from vue component to blade file. I try to create props but it's didn't work for me. Is it any possible to pass the object to props to get the data? I'm new laravel. I want to pass data which subject, message, days, condition and module name to blade file(view).
I kept searching for this but couldn't find an answer that will make this clear.
Thanks!
blade.php
<div id="app">
<email-component
email_edit_route="{{ route('havence.automail.edit',['id'=>$mailTemplates->id]) }}"
>
</email-component>
</div>
Vue.js
<script>
import Vue from 'vue'
import axios from 'axios'
import MarkdownIt from 'markdown-it'
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
var msg_editor;
const md = new MarkdownIt({
linkify: true
})
export default {
props: ['email_creation_link', 'email_index_route', 'email_edit_route','conditions','modules'],
components: {
},
data() {
return {
template: {
subject: '',
message: '' ,
days: '',
condition_id: 1,
},
options:[
{
display:'Client Name',
actual:'Client name'
},
{
display:'Joined Date',
actual:'Joined date'
},
{
display:'Module Name',
actual:'Module name'
},
{
display:'Last Seen',
actual:'Last seen'
},
],
showName: false,
}
},
mounted(){
var self = this;
ClassicEditor
.create(document.querySelector( "#msg"),
{
})
.then(editor => {
msg_editor = editor;
editor.model.document.on( 'change:data', () => {
self.template.message = msg_editor.getData();
});
})
.catch(error => {
console.error(error);
})
},
methods: {
//Drag items
dragstart: function(item, e){
this.draggingItem = item;
e.dataTransfer.setData('text/plain', item.actual);
},
dragend: function(item,e) {
e.target.style.opacity = 1;
},
dragenter: function(item, e) {
this.draggingItem = item;
},
//content
replaceVariables(input)
{
let updated = input
return updated
},
//hidecontent
showHide: function(e)
{
console.log("Show "+e.target.value+ " fields")
this.showName = e.target.value !== ''
},
fetch()
{
//request data
axios.get(this.email_index_route,this.template)
.then((res) => {
this.template = res.data.template;
})
},
save()
{
//save data to db
axios.post(this.email_index_route, this.templates)
.then((res) => {
alert('Mail sent successfull!')
})
},
addToMail: function(type, text)
{
if (type == 'message') {
this.template.message += text;
msg_editor.setData(this.template.message);
}
},
//user name replace
replaceVariables() {
return this.replaceVariables(this.options || '')
},
}
}
</script>
Quick solution. : why not store data in local storage and fetch it from laravel blade ?
Next solution: you can fire global event from vue and listen on laravel blade .
why dont you send the data through ajax post call and get the data from the controller ?
then pass the data object to blade template.

vue mapGetters not getting on time

I'm using vuex to manage the state in my application and doing one way binding with my form.
<script>
import { mapGetters } from 'vuex'
import store from 'vuex-store'
import DataWidget from '../../../../uiComponents/widget'
export default {
data () {
return {
isEdit: false,
msg: {
id: 0,
content: '',
isEnabled: false
}
}
},
components: {
DataWidget
},
computed: mapGetters({
messageId: 'messageId',
messageContent: 'messageContent',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
methods: {
onSave () {
store.dispatch('saveMessage', this.msg, { root: true })
if (this.isMessageValid) {
this.isEdit = !this.isEdit
}
}
},
created () {
this.msg.id = this.messageId
this.msg.content = this.messageContent
this.msg.isEnabled = this.isMessageEnabled
}
}
</script>
<b-form-textarea id="content" v-model="msg.content" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
On load, the values on created() are not binded until I perform an action on the page or refresh the page.
I have tried mounted () hooked same thing.
My Vuex store (Message Module) looks like this:
const state = {
messageId: 0,
messageContent: '',
isMessageEnabled: false,
isMessageValid: true
}
const getters = {
messageId: state => state.messageId,
messageContent: state => state.messageContent,
isMessageEnabled: state => state.isMessageEnabled,
isMessageValid: state => state.isMessageValid
}
const actions = {
getMessage ({commit, rootGetters}) {
api.fetch('api/Preference/Message', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data)
})
}
}
const mutations = {
[types.MESSAGE_LOAD] (state, payload) {
state.messageId = payload ? payload.id : 0
state.messageContent = payload ? payload.content : ''
state.isMessageEnabled = payload ? payload.enabled : false
}
}
export default {
state,
getters,
actions,
mutations
}
and I have a global action (action.js) the gets multiple data:
export const loadSetting = ({ commit, rootGetters }) => {
api.fetchAsync('api/Preference/all', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data.message)
commit(types.HELPDESK_LOAD, data.helpDesk)
commit(types.VOLUME_LOAD, data.volumes)
commit(types.DOWNLOAD_LOAD, data.downloadService)
})
}
My api call:
async fetchAsync (url, token = '') {
let data = await axios.get(HOST + url, {
headers: {
'Authorization': 'bearer ' + token
}
})
return data
}
The problem is your'e calling an async method in Vuex but in the created method, you're treating it like a sync operation and expect to get a value.
You need to use the computed properties you created since they are reactive and will update on every change. In order to make the computed writeable change it to be like this:
computed: {
...mapGetters({
messageId: 'messageId',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
messageContent(){
get () {
return this.$store.getters.messageContent
},
set (value) {
//this is just an example, you can do other things here
this.$store.commit('updateMessage', value)
}
}
}
And change the html to use messageContent:
<b-form-textarea id="content" v-model="messageContent" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
For more info refer to this: https://vuex.vuejs.org/en/forms.html

Cannot add to Vuex store correctly

I have this Vuex 2 store:
const datastore = new Vuex.Store({
state: {
socketcluster: {
connection: false,
channels: []
},
selected_offers: []
},
mutations: {
addOffer: function(offer) {
datastore.state.selected_offers.push(offer) // first problem: cannot just use state.offers as it throws an undefined
}
},
getters: {
getOffers: function(){
return datastore.state.selected_offers;
}
}
});
And inside a Vue 2 component I do:
methods: {
clicked: function(){
console.log("Toggle Offer");
if ( datastore.getters.getOffers.filter(function(o){ o.id == this.offer.id } ).length == 0 ) {
// add it
datastore.commit('addOffer', this.offer)
} else {
// TODO remove it
}
}
}
What happens is that when I trigger the method, the store changes as follows:
What am I doing wrong?
This is a simple way to work with vuex pattern, In big applications you should use actions instead of mutating the state directly from the component "like I did ", if so i urge you to read more about vuex.
const store = new Vuex.Store({
state: {
socketcluster: {
connection: false,
channels: []
},
selected_offers: [ "offer1", "offer2"]
},
mutations: {
addOffer: function( state, offer ) {
state.selected_offers.push(offer);
}
},
getters: {
getOffers: function( state ){
return state.selected_offers;
}
}
});
new Vue({
store,
data: function() {
return {
offer: "offer3"
}
},
methods: {
clicked: function() {
if ( this.offers.length === 2 ) {
this.$store.commit('addOffer', this.offer)
} else {
// TODO remove it
}
}
},
computed: {
offers: function() {
return this.$store.getters.getOffers;
}
}
}).$mount( "#app" );
<script src="https://vuejs.org/js/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<div v-for="offer in offers" > {{offer}}</div>
<button #click="clicked"> clicked </button>
</div>
The first parameter passed to a mutation is the state object. So, you're pushing the entire state object to the selected_offers array.
Your mutation method should look like this:
mutations: {
addOffer: function(state, offer) {
state.selected_offers.push(offer)
}
},
Here's the documentation for vuex mutations.

Categories