How to dynamic change state in another vuex component? - javascript

I've got a problem with state in component. I have websocket and over it come changes, which I put it in some state. It's ok in one component, it dynamically changes value. But, when I go to the next component (vue-router). Its state changes as well, but is not dynamic. hmmmmm... in console.log changes coming, but not change value in another component.
How can I make it?
Let's see some code:
Here my action, with change states
actions: {
play(ctx, array){
axios.get('http://localhost/task_run?task_id='+array.id)
var conn = new WebSocket('ws://localhost:8080', "protocol");
conn.onmessage = function (ev) {
ctx.commit('procent', {key:array.key, val:ev.data});
ctx.commit('procentOne', {key:array.key, val:ev.data});
console.log('Message: ', ev);
};
},
},
mutations: {
procent(state, val){
var array = JSON.parse(val.val);
state.process[val.key] = array.procent;
state.processOnePersone[array.comp] = array.procent;
}
},
state: {
process: [],
processOnePersone:[],
},
getters: {
process(state){
return state.process
},
processOnePersone(state){
return state.processOnePersone;
}
}
I have one compenent, where it works
<v-progress-circular
:rotate="-90"
:size="50"
:width="5"
:value="process[key]"
color="primary"
>
{{ process[key] }}
</v-progress-circular>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'taskListComponent',
computed: {
...mapGetters(['process',]),
},
}
And component where it doesn't work
<v-progress-circular
:rotate="-90"
:size="50"
:width="5"
:value="processOnePersone[key]"
color="primary"
>
{{ processOnePersone[key] }}
</v-progress-circular>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'queueComponent',
computed: {
...mapGetters(['processOnePersone',]),
},
}

Related

Issue when trying to interact with an API in Vuejs?

datalist.js
import axios from "axios";
export const datalist = () => {
return axios.get("myapiurl/name...").then((response) => response);
};
HelloWorld.vue
<template>
<div>
<div v-for="item in items" :key="item.DttID">
<router-link
:to="{
name: 'UserWithID',
params: { id: item.DepaD },
query: { DepaD: item.DepaID },
}"
>
<div class="bt-color">{{ item.DepaName }}</div>
</router-link>
</div>
<br /><br /><br />
<User />
</div>
</template>
<script>
import User from "./User.vue";
import { datalist } from "./datalist";
export default {
name: "HelloWorld",
components: {
User,
},
data() {
return {
items: datalist,
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
};
</script>
User.vue
<template>
<div>
<div v-for="(item, key) in user" :key="key">
{{ item.Accv }}
</div>
</div>
</template>
<script>
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
lists: datalist,
};
},
computed: {
user: function () {
return this.lists.filter((item) => {
if (item.DepaD === this.$route.params.id) {
return item;
}
});
},
},
};
</script>
Error with the code is,
[Vue warn]: Error in render: "TypeError: this.lists.filter is not a function"
TypeError: this.lists.filter is not a function
The above error i am getting in User.vue component in the line number '20'
From the api which is in, datalist.js file, i think i am not fetching data correctly. or in the list filter there is problem in User.vue?
Try to change the following
HelloWorld.vue
data() {
return {
items: [],
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
User.vue
data() {
return {
lists: []
};
},
mounted() {
datalist().then((r) => {
this.lists = r.data;
});
},
At least this suppress the error, but i cant tell more based on your snippet since there are network issues :)
Since your datalist function returns a Promise, you need to wait for it to complete. To do this, simply modify your component code as follows:
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
// empty array on initialization
lists: [],
};
},
computed: {
user: function() {
return this.lists.filter((item) => {
if (item.DeploymentID === this.$route.params.id) {
return item;
}
});
},
},
// asynchronous function - because internally we are waiting for datalist() to complete
async-mounted() {
this.users = await datalist() // or datalist().then(res => this.users = res) - then async is not needed
}
};
now there will be no errors when initializing the component, since initially lists is an empty array but after executing the request it will turn into what you need.
You may define any functions and import them, but they wont affect until you call them, in this case we have datalist function imported in both HelloWorld and User component, but it did not been called in User component. so your code:
data() {
return {
lists: datalist,
};
},
cause lists to be equal to datalist that is a function, no an array! where .filter() should be used after an array, not a function! that is the reason of error.
thus you should call function datalist and put it's response in lists instead of putting datalist itself in lists
Extra:
it is better to call axios inside the component, in mounted, created or ...
it is not good idea to call an axios command twice, can call it in HelloWorl component and pass it to User component via props

Vue SPA - call method from any component

I'm working on a notification functionality that can be called from any component. It's a simple Vuetify v-snackbar.
In App.vue
<router-view :key="$route.fullPath"></router-view>
<v-snackbar :value="showNotification" :multi-line="false">{{
notificationText
}}
</v-snackbar>
and method
methods:{
notify(text){
this.notificationText = text
this.showNotification = true
}
}
this obviously work inside App.vue but I'd like to call notify from any component. How can I do that? I use router
In main.js add the global properties :
import Vue from 'vue'
Vue.prototype.$notificationText = ''
Vue.prototype.$showNotification = false
....
App.vue
<router-view :key="$route.fullPath"></router-view>
<v-snackbar :value="$showNotification" :multi-line="false">{{
$notificationText
}}
</v-snackbar>
then any component do :
methods:{
notify(text){
this.$notificationText = text
this.$showNotification = true
}
}
You can use a state manager (vuex) for this functionality.
// store.js
const store = new Vuex.Store({
state: {
showNotification: false,
notificationText: ''
},
mutations: {
changeNotificationState (state, value) {
state.showNotification = true
},
changeNotificationText (state, value) {
state.notificationText = value
}
},
actions: {
notify({ commit }, text) {
commit('changeNotificationState', true)
commit('changeNotificationText', 'some text')
}
}
})
<router-view :key="$route.fullPath"></router-view>
<v-snackbar :value="$store.state.showNotification" :multi-line="false">{{
$store.state.notificationText
}}
</v-snackbar>
Now from every component in your app you can call the notification function like this:
this.$store.dispatch('notify', 'notification text')

Make a reactive component with vuejs

I need a Vue component to show some HTML content in v-data-table from Vuetify. I have seen this post Vue 2 contentEditable with v-model, and I created a similar code shown below.
My problem is the component is not reactive. When I click the "Test button", no content is updated in HtmlTextArea.
<template>
<div>
<v-btn #click="doTest()">Test Button</v-btn>
<HtmlTextArea
v-model="content"
style="max-height:50px;overflow-y: scroll;"
></HtmlTextArea>
</div>
<template>
export default {
name: "ModelosAtestados",
components: { HtmlTextArea },
data: () => ({
content: "",
}),
methods: {
doTest() {
this.content = "kjsadlkjkasfdkjdsjkl";
},
},
};
//component
<template>
<div ref="editable" contenteditable="false" v-on="listeners"></div>
</template>
<script>
export default {
name: "HtmlTextArea",
props: {
value: {
type: String,
default: "",
},
},
computed: {
listeners() {
return { ...this.$listeners, input: this.onInput };
},
},
mounted() {
this.$refs.editable.innerHTML = this.value;
},
methods: {
onInput(e) {
this.$emit("input", e.target.innerHTML);
},
},
};
</script>
This occurs because HtmlTextArea sets the div contents to its value prop only in the mounted lifecycle hook, which is not reactive.
The fix is to setup a watcher on value, so that the div contents are updated to match whenever a change occurs:
// HtmlTextArea.vue
export default {
watch: {
value: {
handler(value) {
this.$refs.editable.innerHTML = value;
}
}
}
}
demo
In the #click event binder, you have to pass a function. You passed the result of an executed function.
To make it work: #click="doTest" or #click="() => doTest()".
How to debug such problems:
Display the value you want to update on your template to check if its updated: {{content}}
Use the vue devtool extension to check the current state of your components

Vue.js Component rendering after prop update

In Vue.js i have a component (Answer Component) like this:
<template>
<a class="quiz-input-choice" :class="{'quiz-input-choice--selected': answer.selected}"
#click="toggleSelect()" :selected="answer.selected">
<img :src="answer.image_path"/>
<p class="quiz-input-choice__description">{{answer.title}}</p>
</a>
</template>
<script>
export default {
props: ['answer'],
methods: {
toggleSelect() {
this.$parent.$emit('answer-selected', this.answer.id);
}
}
}
</script>
If in the parent (Question Component) I update the "selected" attribute of the element, this component will not be rerendered.
export default {
props: ['question'],
components: {QuizAnswer},
created: function () {
let _self = this;
this.$on('answer-selected', id => {
let i = _self.question.answers.map(item => item.id).indexOf(id);
let answer = _self.question.answers[i];
answer.selected = !answer.selected;
});
}
}
In Vue Developer Console, i checked that Answer component data are updated, so the answer is marked as selected. Anyway, is not rendered with the "quiz-input-choice--selected" class.
If, strangely, I update from the parent other attribute of the prop (for example (answer.title), then the child component is rendered correctly with also the class "quiz-input-choice--selected".
So i guess it's a problem of detecting changes from the child.
Thank you everybody for the answers.
I discovered the problem. The "selected" attribute of the answer was not present in the initial object, so Vue cannot make reactive that attribute.
https://v2.vuejs.org/v2/guide/reactivity.html
I solved making reactive that property in the parent component.
created() {
let self = this;
this.question.answers.forEach(function (answer) {
self.$set(answer, 'selected', false);
});
},
I think you have a structural issue here. You shouldn't submit an event to your parent, since the component is supposed to be self-contained.
What you can do however is emitting an event in the child component (Answer) that will be catch in the parent (Question).
Answer.vue
<template>
<a class="quiz-input-choice" :class="{'quiz-input-choice--selected': answer.selected}"
#click="toggleSelect()" :selected="answer.selected">
<img :src="answer.image_path"/>
<p class="quiz-input-choice__description">{{answer.title}}</p>
</a>
</template>
<script>
export default {
props: ['answer'],
methods: {
toggleSelect() {
this.$emit('answer-selected');
}
}
}
</script>
Question.vue
Your template will have be catching the event like this (I don't know where your answers are so I assume that you have a answers array) :
<answer
v-for="(answer, index) in answers"
:answer="answer"
#answer-selected="answerSelected(index)"
></answer>
And your script will look like this :
export default {
props: ['question'],
components: {QuizAnswer},
data() {
return {
answers: [],
selectedAnswer: -1,
};
},
watch: {
selectedAnswer(newIndex, oldIndex) {
if (oldIndex > -1 && this.answers.length > oldIndex) {
// Reset old value
this.answers[oldIndex].selected = false;
}
if (newIndex > -1 && this.answers.length > newIndex) {
// Set new value
this.answers[newIndex].selected = true;
}
},
},
methods: {
answerSelected(index) {
this.selectedAnswer = index;
},
},
};

Updating VueJS component data attributes when prop updates

I'm building a VueJS component which needs to update the data attributes when a prop is updated however, it's not working as I am expecting.
Basically, the flow is that someone searches for a contact via an autocomplete component I have, and if there's a match an event is emitted to the parent component.
That contact will belong to an organisation and I pass the data down to the organisation component which updates the data attributes. However it's not updating them.
The prop being passed to the organisation component is updated (via the event) but the data attibute values is not showing this change.
This is an illustration of my component's structure...
Here is my code...
Parent component
<template>
<div>
<blink-contact
:contact="contact"
v-on:contactSelected="setContact">
</blink-contact>
<blink-organisation
:organisation="organisation"
v-on:organisationSelected="setOrganisation">
</blink-organisation>
</div>
</template>
<script>
import BlinkContact from './BlinkContact.vue'
import BlinkOrganisation from './BlinkOrganisation.vue'
export default {
components: {BlinkContact, BlinkOrganisation},
props: [
'contact_id', 'contact_name', 'contact_tel', 'contact_email',
'organisation_id', 'organisation_name'
],
data () {
return {
contact: {
id: this.contact_id,
name: this.contact_name,
tel: this.contact_tel,
email: this.contact_email
},
organisation: {
id: this.organisation_id,
name: this.organisation_name
}
}
},
methods: {
setContact (contact) {
this.contact = contact
this.setOrganisation(contact.organisation)
},
setOrganisation (organisation) {
this.organisation = organisation
}
}
}
</script>
Child component (blink-organisation)
<template>
<blink-org-search
field-name="organisation_id"
:values="values"
endpoint="/api/v1/blink/organisations"
:format="format"
:query="getQuery"
v-on:itemSelected="setItem">
</blink-org-search>
</template>
<script>
export default {
props: ['organisation'],
data() {
return {
values: {
id: this.organisation.id,
search: this.organisation.name
},
format: function (items) {
for (let item of items.results) {
item.display = item.name
item.resultsDisplay = item.name
}
return items.results
}
}
},
methods: {
setItem (item) {
this.$emit('organisationSelected', item)
}
}
}
</script>
How can I update the child component's data properties when the prop changes?
Thanks!
Use a watch.
watch: {
organisation(newValue){
this.values.id = newValue.id
this.values.search = newValue.name
}
}
In this case, however, it looks like you could just use a computed instead of a data property because all you are doing is passing values along to your search component.
computed:{
values(){
return {
id: this.organisation.id
search: this.organisation.name
}
}
}

Categories

Resources