Vue not reacting to a computed props change - javascript

I am using the Vue composition API in one of my components and am having some trouble getting a component to show the correct rendered value from a computed prop change. It seems that if I feed the prop directly into the components render it reacts as it should but when I pass it through a computed property it does not.
I am not sure why this is as I would have expected it to be reactive in the computed property too?
Here is my code:
App.vue
<template>
<div id="app">
<Tester :testNumber="testNumber" />
</div>
</template>
<script>
import Tester from "./components/Tester";
export default {
name: "App",
components: {
Tester,
},
data() {
return {
testNumber: 1,
};
},
mounted() {
setTimeout(() => {
this.testNumber = 2;
}, 2000);
},
};
</script>
Tester.vue
<template>
<div>
<p>Here is the number straight from the props: {{ testNumber }}</p>
<p>
Here is the number when it goes through computed (does not update):
{{ testNumberComputed }}
</p>
</div>
</template>
<script>
import { computed } from "#vue/composition-api";
export default {
props: {
testNumber: {
type: Number,
required: true,
},
},
setup({ testNumber }) {
return {
testNumberComputed: computed(() => {
return testNumber;
}),
};
},
};
</script>
Here is a working codesandbox:
https://codesandbox.io/s/vue-composition-api-example-forked-l4xpo?file=/src/components/Tester.vue
I know I could use a watcher but I would like to avoid that if I can as it's cleaner the current way I have it

Don't destruct the prop in order to keep its reactivity setup({ testNumber }) :
setup(props) {
return {
testNumberComputed: computed(() => {
return props.testNumber;
}),
};
}

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

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

Why not my vue component not re-rendering?

I have a question why not this component, not re-rendering after changing value so what I'm trying to do is a dynamic filter like amazon using the only checkboxes so let's see
I have 4 components [ App.vue, test-filter.vue, filtersInputs.vue, checkboxs.vue]
Here is code sandbox for my example please check the console you will see the value changing https://codesandbox.io/s/thirsty-varahamihira-nhgex?file=/src/test-filter/index.vue
the first component is App.vue;
<template>
<div id="app">
<h1>Filter</h1>
{{ test }}
<test-filter :filters="filters" :value="test"></test-filter>
</div>
</template>
<script>
import testFilter from "./test-filter";
import filters from "./filters";
export default {
name: "App",
components: {
testFilter,
},
data() {
return {
filters: filters,
test: {},
};
},
};
</script>
so App.vue that holds the filter component and the test value that I want to display and the filters data is dummy data that hold array of objects.
in my test-filter component, I loop throw the filters props and the filterInputs component output the input I want in this case the checkboxes.
test-filter.vue
<template>
<div class="test-filter">
<div
class="test-filter__filter-holder"
v-for="filter in filters"
:key="filter.id"
>
<p class="test-filter__title">
{{ filter.title }}
</p>
<filter-inputs
:value="value"
:filterType="filter.filter_type"
:options="filter.options"
#checkboxChanged="checkboxChanged"
></filter-inputs>
</div>
</div>
<template>
<script>
import filterInputs from "./filterInputs";
export default {
name: "test-filter",
components: {
filterInputs,
},
props:{
filters: {
type: Array,
default: () => [],
},
value: {
type: Array,
default: () => ({}),
},
},
methods:{
checkboxChanged(value){
// Check if there is a array in checkbox key if not asssign an new array.
if (!this.value.checkbox) {
this.value.checkbox = [];
}
this.value.checkbox.push(value)
}
};
</script>
so I need to understand why changing the props value also change to the parent component and in this case the App.vue and I tried to emit the value to the App.vue also the component didn't re-render but if I check the vue dev tool I see the value changed but not in the DOM in {{ test }}.
so I will not be boring you with more code the filterInputs.vue holds child component called checkboxes and from that, I emit the value of selected checkbox from the checkboxes.vue to the filterInputs.vue to the test-filter.vue and every component has the value as props and that it if you want to take a look the rest of components I will be glad if you Did.
filterInpust.vue
<template>
<div>
<check-box
v-if="filterType == checkboxName"
:options="options"
:value="value"
#checkboxChanged="checkboxChanged"
></check-box>
</div>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: () => ({}),
},
options: {
type: Array,
default: () => [],
},
methods: {
checkboxChanged(value) {
this.$emit("checkboxChanged", value);
},
},
}
</script>
checkboxes.vue
<template>
<div>
<div
v-for="checkbox in options"
:key="checkbox.id"
>
<input
type="checkbox"
:id="`id_${_uid}${checkbox.id}`"
#change="checkboxChange"
:value="checkbox"
/>
<label
:for="`id_${_uid}${checkbox.id}`"
>
{{ checkbox.title }}
</label>
</div>
</div>
<template>
<script>
export default {
props: {
value: {
type: Object,
default: () => ({}),
},
options: {
type: Array,
default: () => [],
},
}
methods: {
checkboxChange(event) {
this.$emit("checkboxChanged", event.target.value);
},
},
};
</script>
I found the solution As I said in the comments the problem was that I'm not using v-model in my checkbox input Vue is a really great framework the problem wasn't in the depth, I test the v-model in my checkbox input and I found it re-render the component after I select any checkbox so I search more and found this article and inside of it explained how we can implement v-model in the custom component so that was the solution to my problem and also I update my codeSandbox Example if you want to check it out.
Big Thaks to all who supported me to found the solution: sarkiroka, Jakub A Suplick

Vuejs component cannot access its own data

main component:
<template>
<spelling-quiz v-bind:quiz="quiz"></spelling-quiz>
</template>
<script>
var quiz = {
text: "blah:,
questions: ['blah blah']
}
import spellingQuiz1 from './spellingQuiz1.vue';
export default {
components: {
spellingQuiz: spellingQuiz1
},
data: function(){
return{
quiz: quiz
}
}
};
</script>
spelling-quiz component - HTML
<template>
<div>
<br>
<div v-for="(question, index) in quiz.questions">
<b-card v-bind:header="question.text"
v-show="index === qIndex"
class="text-center">
<b-form-group>
<b-form-radio-group
buttons
stacked
button-variant="outline-primary"
size="lg"
v-model="userResponses[index]"
:options="question.options" />
</b-form-group>
<button v-if="qIndex > 0" v-on:click="prev">
prev
</button>
<button v-on:click="next">
next
</button>
</b-card>
</div>
<b-card v-show="qIndex === quiz.questions.length"
class="text-center" header="Quiz finished">
<p>
Total score: {{ score() }} / {{ quiz.questions.length }}
</p>
</b-card>
</div>
</template>
spelling-quiz component - JS
<script>
export default{
props:{
quiz: {
type: Object,
required: true
},
data: function(){
return{
qIndex: 0,
userResponses: Array(quiz.questions.length).fill(false)
};
},
methods:{
next(){
this.qIndex++;
},
prev(){
this.qIndex--;
},
score(){
return this.userResponses.filter(function(val){return val == 'correct'}).length;
}
}
}
};
</script>
I am getting the following error:
[Vue warn]: Property or method "qIndex" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I am also getting the same error for "userReponses".
I do not understand the error, and I have some research but the examples do not really apply to my issue.
Question:
Why is my data not accessible? If I refer to just this component it works, but as a child component it throws this error. I am not sure how I can fix it.
You have a missing } after the props. Currently your data attribute live in your props attribute. data should live on the root object. Should be structured like this:
export default {
name: "HelloWord",
props: {
quiz: {
type: Object,
required: true
}
},
data: function() {
return {
test: 100
};
},
methods: {
next() {},
prev() {},
score() {}
}
};

Passing props dynamically to dynamic component in VueJS

I've a dynamic view:
<div id="myview">
<div :is="currentComponent"></div>
</div>
with an associated Vue instance:
new Vue ({
data: function () {
return {
currentComponent: 'myComponent',
}
},
}).$mount('#myview');
This allows me to change my component dynamically.
In my case, I have three different components: myComponent, myComponent1, and myComponent2. And I switch between them like this:
Vue.component('myComponent', {
template: "<button #click=\"$parent.currentComponent = 'myComponent1'\"></button>"
}
Now, I'd like to pass props to myComponent1.
How can I pass these props when I change the component type to myComponent1?
To pass props dynamically, you can add the v-bind directive to your dynamic component and pass an object containing your prop names and values:
So your dynamic component would look like this:
<component :is="currentComponent" v-bind="currentProperties"></component>
And in your Vue instance, currentProperties can change based on the current component:
data: function () {
return {
currentComponent: 'myComponent',
}
},
computed: {
currentProperties: function() {
if (this.currentComponent === 'myComponent') {
return { foo: 'bar' }
}
}
}
So now, when the currentComponent is myComponent, it will have a foo property equal to 'bar'. And when it isn't, no properties will be passed.
You can also do without computed property and inline the object.
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
Shown in the docs on V-Bind - https://v2.vuejs.org/v2/api/#v-bind
You could build it like...
comp: { component: 'ComponentName', props: { square: true, outlined: true, dense: true }, model: 'form.bar' }
<component :is="comp.component" v-bind="{...comp.props}" v-model="comp.model"/>
I have the same challenge, fixed by the following:
<component :is="currentComponent" v-bind="resetProps">
{{ title }}
</component>
and the script is
export default {
…
props:['title'],
data() {
return {
currentComponent: 'component-name',
}
},
computed: {
resetProps() {
return { ...this.$attrs };
},
}
<div
:color="'error'"
:onClick="handleOnclick"
:title="'Title'"
/>
I'm came from reactjs and I found this solve my issue
If you have imported you code through require
var patientDetailsEdit = require('../patient-profile/patient-profile-personal-details-edit')
and initalize the data instance as below
data: function () {
return {
currentView: patientDetailsEdit,
}
you can also reference the component through the name property if you r component has it assigned
currentProperties: function() {
if (this.currentView.name === 'Personal-Details-Edit') {
return { mode: 'create' }
}
}
When you use the <component> inside a v-for you can change the answer of thanksd as follow:
methods: {
getCurrentProperties(component) {
if (component === 'myComponent') {
return {foo: baz};
}
}
},
usage
<div v-for="object in object.items" :key="object._your_id">
<component :is="object.component" v-bind="getCurrentProperties(object.component)" />
</div>
Let me know if there is an easier way.

Categories

Resources