Accessing data in created from props Vue.js - vue.js

I am not able to access data from props in created function.
Its working fine in methods as you can see in below code.
And its working in methods as you see below
export default {
props: ['projectId'],
data() {
return {
elements: []
}
},
created() {
axios.get(`api/projects/${this.projectId}/elements`).then(response => {
this.elements = response.data.data
});
},
methods: {
addingElement(element) {
alert(this.projectId);
}
}
}
Parent template
<add-project-element :projectId="project.id"></add-project-element>
Thanks

<add-project-element :projectId="project.id"></add-project-element> has to be
<add-project-element :project-id="project.id"></add-project-element>
HTML attribute names are case-insensitive. Any uppercase character will be interpreted as lowercase. So camelCased prop names need to use their kebab-cased equivalents.

There is a typo in your axios request. Your prop is projectId but you have project.id there...

Ok so finally I found something, I am not sure if its right but its working fine.
actually, props are rendering after created that's why its shows undefine. so I set some time. and it worked
created() {
setTimeout(() => {
axios.get(`/api/projects/${self.projectId}/elements`)
.then(response => {
this.elements = response.data.data
});
}, 1)
},
Thanks everyone

Related

How to generate computed props on the fly while accessing the Vue instance?

I was wondering if there is a way of creating computed props programatically, while still accessing the instance to achieve dynamic values
Something like that (this being undefined below)
<script>
export default {
computed: {
...createDynamicPropsWithTheContext(this), // helper function that returns an object
}
}
</script>
On this question, there is a solution given by Linus: https://forum.vuejs.org/t/generating-computed-properties-on-the-fly/14833/4 looking like
computed: {
...mapPropsModels(['cool', 'but', 'static'])
}
This works fine but the main issue is that it's fully static. Is there a way to access the Vue instance to reach upon props for example?
More context
For testing purposes, my helper function is as simple as
export const createDynamicPropsWithTheContext = (listToConvert) => {
return listToConvert?.reduce((acc, curr) => {
acc[curr] = curr
return acc
}, {})
}
What I actually wish to pass down to this helper function (via this) are props that are matching a specific prefix aka starting with any of those is|can|has|show (I'm using a regex), that I do have access via this.$options.props in a classic parent/child state transfer.
The final idea of my question is mainly to avoid manually writing all the props manually like ...createDynamicPropsWithTheContext(['canSubmit', 'showModal', 'isClosed']) but have them populated programatically (this pattern will be required in a lot of components).
The props are passed like this
<my-component can-submit="false" show-modal="true" />
PS: it's can-submit and not :can-submit on purpose (while still being hacked into a falsy result right now!).
It's for the ease of use for the end user that will not need to remember to prefix with :, yeah I know...a lot of difficulty just for a semi-colon that could follow Vue's conventions.
You could use the setup() hook, which receives props as its first argument. Pass the props argument to createDynamicPropsWithTheContext, and spread the result in setup()'s return (like you had done previously in the computed option):
import { createDynamicPropsWithTheContext } from './props-utils'
export default {
⋮
setup(props) {
return {
...createDynamicPropsWithTheContext(props),
}
}
}
demo
If the whole thing is for avoiding using a :, then you might want to consider using a simple object (or array of objects) as data source. You could just iterate over a list and bind the data to the components generated. In this scenario the only : used are in the objects
const comps = [{
"can-submit": false,
"show-modal": true,
"something-else": false,
},
{
"can-submit": true,
"show-modal": true,
"something-else": false,
},
{
"can-submit": false,
"show-modal": true,
"something-else": true,
},
]
const CustomComponent = {
setup(props, { attrs }) {
return {
attrs
}
},
template: `
<div
v-bind="attrs"
>{{ attrs }}</div>
`
}
const vm = Vue.createApp({
setup() {
return {
comps
}
},
template: `
<custom-component
v-for="(item, i) in comps"
v-bind="item"
></custom-component>
`
})
vm.component('CustomComponent', CustomComponent)
vm.mount('#app')
<script src="https://unpkg.com/vue#3"></script>
<div id="app">{{ message }}</div>
Thanks to Vue's Discord Cathrine and skirtle folks, I achieved to get it working!
Here is the thread and here is the SFC example that helped me, especially this code
created () {
const magicIsShown = computed(() => this.isShown === true || this.isShown === 'true')
Object.defineProperty(this, 'magicIsShown', {
get () {
return magicIsShown.value
}
})
}
Using Object.defineProperty(this... is helping keeping the whole state reactive and the computed(() => can reference some other prop (which I am looking at in my case).
Using a JS object could be doable but I have to have it done from the template (it's a lower barrier to entry).
Still, here is the solution I came up with as a global mixin imported in every component.
// helper functions
const proceedIfStringlean = (propName) => /^(is|can|has|show)+.*/.test(propName)
const stringleanCase = (string) => 'stringlean' + string[0].toUpperCase() + string.slice(1)
const computeStringlean = (value) => {
if (typeof value == 'string') {
return value == 'true'
}
return value
}
// the actual mixin
const generateStringleans = {
created() {
for (const [key, _value] of Object.entries(this.$props)) {
if (proceedIfStringlean(key)) {
const stringleanComputed = computed(() => this[key])
Object.defineProperty(this, stringleanCase(key), {
get() {
return computeStringlean(stringleanComputed.value)
},
// do not write any `set()` here because this is just an overlay
})
}
}
},
}
This will scan every .vue component, get the passed props and if those are prefixed with either is|can|has|show, will create a duplicated counter-part with a prefix of stringlean + pass the initial prop into a method (computeStringlean in my case).
Works great, there is no devtools support as expected since we're wiring it directly in vanilla JS.

Vue component gets prop only if data attributes not set

I need help. I'm kind of an amateur in Vue3, and can´t understand why this happens:
If I set this in the parent component:
props: [ 'code' ],
data() {
return {
asset: {
id: '',
brand: '',
group: {
name: '',
area: ''
}
}
}
},
created() {
axios.get('/api/myUrl/' + this.code, {})
.then(response => {
if (response.status === 200) {
this.asset = response.data;
}
})
.catch(error => {
console.log(error);
})
}
then, in my component <asset-insurance :asset_id="asset.id"></asset-insurance>, asset_id prop is empty.
But, if I set:
props: [ 'code' ],
data() {
return {
asset: []
}
},
created() {
axios.get('/api/myUrl/' + this.code, {})
.then(response => {
if (response.status === 200) {
this.asset = response.data;
}
})
.catch(error => {
console.log(error);
})
}
then, the asset_id prop gets the correct asset.id value inside <asset-insurance> component, but I get a few warnings and an error in the main component about asset property group.name not being set (but it renders correctly in template).
Probably I'm doing something terribly wrong, but I can't find where is the problem. Any help?
Edit:
I'm checking the prop in child component AssetInsurance just by console.logging it
<script>
export default {
name: "AssetInsurance",
props: [
'asset_id'
],
created() {
console.log(this.asset_id)
}
}
</script>
asset_id is just an integer, and is being assigned correctly in parent's data asset.id, because I'm rendering it in the parent template too.
It's incorrect to define asset as an array and reassign it with plain object. This may affect reactivity and also prevents nested keys in group from being read.
The problem was misdiagnosed. asset_id value is updated but not at the time when this is expected. Prop value is not available at the time when component instance is created, it's incorrect to check prop value in created, especially because it's set asynchronously.
In order to output up-to-date asset_id value in console, a watcher should be used, this is what they are for. Otherwise asset_id can be used in a template as is.
Ok, I think I found the proper way, at least in my case.
Prop is not available in the component because it is generated after the creation of the component, as #EstusFlask pointed.
The easy fix for this is to mount the component only when prop is available:
<asset-insurance v-if="asset.id" :asset_id="asset.id"></asset-insurance>
This way, components are running without problem, and I can declare objects as I should. :)
Thank you, Estus.

How to initialize data with computed value inside asyncData?

I am building a web app with nuxt.
here's simplified code:
pages/index.vue
data() {
return {
item: {name:'', department: '', testField: '',},
}
}
async asyncData() {
const result = call some API
const dataToInitialize = {
name: result.username,
department: result.department,
testField: //want to assign computed value
}
return {item: dataToInitialize}
}
Inside asyncData, I call API and assign value to dataToInitialize.
dataToInitialize has testField field, and I want to assign some computed value based on username and department.
(for example, 'a' if name starts with 'a' and department is 'management'..etc there's more complicated logic in real scenario)
I have tried to use computed property , but I realized that asyncData cannnot access computed.
Does anyone know how to solve this?
Any help would be appreciated!
=======
not sure if it's right way, but I solved the issue by setting 'testfield' inside created.
created() {
this.item.testField = this.someMethod(this.item);
},
Looking at the Nuxt lifecyle, you can see that asyncData is called before even a Vue instance is mounted on your page.
Meanwhile, fetch() hook is called after. This is non-blocking but more flexible in a lot of ways.
An alternative using fetch() would look like this
<script>
export default {
data() {
return {
staticVariable: 'google',
}
},
async fetch() {
await this.$axios(this.computedVariable)
},
computed: {
computedVariable() {
return `www.${this.staticVariable}.com`
},
},
}
</script>
Another alternative, would be to use URL query string or params, thanks to Vue-router and use those to build your API call (in an asyncData hook).
Here is an example on how to achieve this: https://stackoverflow.com/a/68112290/8816585
EDIT after comment question
You can totally use a computed inside of a fetch() hook indeed. Here is an example on how to achieve this
<script>
export default {
data() {
return {
test: 'test',
}
},
async fetch() {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${this.nice}`)
console.log(await response.json())
},
computed: {
nice() {
return this.test + 'wow!'
},
},
}
</script>
I found that destructuring fetch({}) causes issues with accessing this inside fetch scope ->
async fetch({ store, $anyOtherGlobalVar }){
store.dispatch...
// destructuring approach changes the scope of the function and `this` does not have access to data, computed and e.t.c
}
If you want to access this scope for example this.data, avoid destructuring and access everything through this.
async fetch() {
this.$store...
this.data...
}

Vue reactivity issues in component making asynchronous server requests

A small amount of context: I have a Vue view called Articles. When the component is mounted to the DOM, I fetch all posts from the database using the axios library (in conjunction with Laravel controllers and API routes). The articles view contains a data property called active, which points towards the post that is currently selected. Clicking on a different post in the sidebar updates active and subsequently the new post is shown.
Now, every post has many comments, and those comments in turn can be linked to subcomments if you will. However, the mounted lifecycle hook in Articles.vue gets invoked only once and when I try to place the server request in updated(), everything seemingly works but I'd eventually get a 429 status (too many requests). My guess is that for each comment that is retrieved, the code in updated() get's invoked again.
I guess my question is as follows: How can I make Post.vue reactive, since right now the mounted lifecycle hook will be invoked only once even when another post is selected.
Here's the code:
Articles.vue
export default {
name: "Articles",
components: {SidebarLink, PageContent, Sidebar, Post, Searchbar, Spinner},
data() {
return {
posts: [],
active: undefined,
loading: true
}
},
mounted() {
this.fetchPosts();
},
methods: {
async fetchPosts() {
const response = await this.$http.get('/api/posts');
this.posts = response.data;
this.active = this.posts[0];
setTimeout(() => {
this.loading = false;
}, 400);
},
showPost(post) {
this.active = post;
}
}
}
Post.vue
export default {
name: "Post",
components: {Tag, WennekesComment},
props: ['post'],
data() {
return {
expanded: true,
comments: []
}
},
mounted() {
this.fetchComments();
},
methods: {
async fetchComments() {
let response = await this.$http.get('/api/posts/' + this.post.id + '/comments');
this.comments = response.data;
}
}
}
WennekesComment.vue
export default {
name: "WennekesComment",
props: ['comment'],
data() {
return {
subComments: []
}
},
mounted() {
this.fetchSubcomments();
},
methods: {
fetchSubcomments() {
let response = this.$http.get('/api/comments/' + this.comment.id).then((result) => {
// console.log(result);
});
}
}
}
Template Logic
<wennekes-comment v-for="comment in comments" :key="comment.id" :comment="comment"></wennekes-comment>
<post v-if="!loading" :post="active" :key="active.id"/>
Thanks in advance, and my apologies if this question is somewhat unclear, I'm somewhat at a loss.
Regards,
Ryan
UPDATE
I think I got it to work. In Articles.vue, I have appended a key to the post component. I think this is Vue's way of knowing which specific instance of a component to update.
I think I got it to work. In Articles.vue, I have appended a key to the post component. I think this is Vue's way of knowing which specific instance of a component to update.

vuejs2: how can i destroy a watcher?

How can i destroy this watcher? I need it only one time in my child component, when my async data has loaded from the parent component.
export default {
...
watch: {
data: function(){
this.sortBy();
},
},
...
}
gregor ;)
If you construct a watcher dynamically by calling vm.$watch function, it returns a function that may be called at a later point in time to disable (remove) that particular watcher.
Don't put the watcher statically in the component, as in your code, but do something like:
created() {
var unwatch = this.$watch(....)
// now the watcher is watching and you can disable it
// by calling unwatch() somewhere else;
// you can store the unwatch function to a variable in the data
// or whatever suits you best
}
More thorough explanation may be found from here: https://codingexplained.com/coding/front-end/vue-js/adding-removing-watchers-dynamically
Here is an example:
<script>
export default {
data() {
return {
employee: {
teams: []
},
employeeTeamsWatcher: null,
};
},
created() {
this.employeeTeamsWatcher = this.$watch('employee.teams', (newVal, oldVal) => {
this.setActiveTeamTabName();
});
},
methods: {
setActiveTeamTabName() {
if (this.employee.teams.length) {
// once you got your desired condition satisfied then unwatch by calling:
this.employeeTeamsWatcher();
}
},
},
};
</script>
If you are using vue2 using the composition-api plugin or vue3, you can use WatchStopHandle which is returned by watch e.g.:
const x = ref(0);
setInterval(() => {
x.value++;
}, 1000);
const unwatch = watch(
() => x.value,
() => {
console.log(x.value);
x.value++;
// stop watch:
if (x.value > 3) unwatch();
}
);
For this kind of stuff, you can investigate the type declaration of the API, which is very helpful, just hover the mouse on it, and it will show you a hint about what you can do: