Vue component gets prop only if data attributes not set - vue.js

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.

Related

When passing data from parent component to child component via props, the data appears to be undefined in the mounted hook of the child component

In my parent component:
<UsersList :current-room="current_room" />
In the child component:
export default {
props: {
currentRoom: Object
},
data () {
return {
users: []
}
},
mounted () {
this.$nextTick( async () => {
console.log(this.currentRoom) // this, weirdly, has the data I expect, and id is set to 1
let url = `${process.env.VUE_APP_API_URL}/chat_room/${this.currentRoom.id}/users`
console.log(url) // the result: /api/chat_room/undefined/users
let response = await this.axios.get(url)
this.users = response.data
})
},
}
When I look at the page using vue-devtools, I can see the data appears:
I've run into this issue in the past – as have many others. For whatever reason, you can't rely on props being available in the component's mounted handler. I think it has to do with the point at which mounted() is called within Vue's lifecycle.
I solved my problem by watching the prop and moving my logic from mounted to the watch handler. In your case, you could watch the currentRoom property, and make your api call in the handler:
export default {
props: {
currentRoom: Object
},
data() {
return {
users: []
}
},
watch: {
currentRoom(room) {
this.$nextTick(async() => {
let url = `${process.env.VUE_APP_API_URL}/chat_room/${room.id}/users`
let response = await this.axios.get(url)
this.users = response.data
})
}
},
}
I don't think you really need to use $nextTick() here, but I left it as you had it. You could try taking that out to simplify the code.
By the way, the reason console.log(this.currentRoom); shows you the room ID is because when you pass an object to console.log(), it binds to that object until it is read. So even though the room ID is not available when console.log() is called, it becomes available before you see the result in the console.

Accessing data in created from props 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

Undefined data when using vuex and vue-router

I'm having an issue with the initial state of data in my application. I'm using vuex and vue-router, and I think the async stuff is tripping me up, but I'm not sure how to fix it.
In my view.vue component:
beforeRouteEnter(to, from, next) {
store.dispatch('assignments/getAssignment', {
id: to.params.id
}).then(res => next());
},
In my module:
getAssignment({commit, state}, {id}) {
return axios.get('/assignments/' + id)
.then(response => {
if(response.data.data.type == 'goal_plan') {
const normalizedEntity = normalize(response.data.data, assignment_schema);
commit('goals/setGoals', {goals: normalizedEntity.entities.goals}, {root: true});
commit('goals/setGoalStrategicPriorities', {goal_priorities: normalizedEntity.entities.strategicPriorities}, {root: true});
commit('goals/setObjectives', {objectives: normalizedEntity.entities.objectives}, {root: true});
commit('goals/setStrategies', {strategies: normalizedEntity.entities.strategies}, {root: true});
}
commit('setAssignment', {assignment: response.data.data});
}).catch(error => {
console.log(error);
EventBus.$emit('error-thrown', error);
});
},
A couple of subcomponents down, I want to access state.goals.goals, but it is initially undefined. I can handle some of the issues from that, but not all.
For example, I have a child component of view.vue that includes
computed: {
originalGoal() {
return this.$store.getters['goals/goalById'](this.goalId);
},
},
data() {
return {
form: {
id: this.originalGoal.id,
description: this.originalGoal.description,
progress_type: this.originalGoal.progress_type,
progress_values: {
to_reach: this.originalGoal.progress_values.to_reach,
achieved: this.originalGoal.progress_values.achieved,
},
due_at: moment(this.originalGoal.due_at).toDate(),
status: this.originalGoal.status,
},
In the heady days before I started using vuex, I was passing in the original goal as a prop, so it wasn't an issue. Since it's now pulled from the state, I get a bunch of errors that it can't find the various properties of undefined. Eventually originalGoal resolves in the display, but it's never going to show up in the form this way.
I tried "watch"ing the computed prop, but I never saw when it changed, and I'm pretty sure that's not the right way to do it anyway.
So, is there a way to get the data set initially? If not, how should I go about setting the form values once the data IS set? (Any other suggestions welcome, as I'm pretty new to vuex and vue-router.)
So if I set the form values in "mounted," I'm able to get this to work. Still learning about the vue life-cycle I guess. :)

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:

mapState with setter

I would like to assign setter methods via mapState. I currently use a workaround where I name the variable that I am interested in (todo) as a temporary name (storetodo) and then refer to it in another computed variable todo.
methods: {
...mapMutations([
'clearTodo',
'updateTodo'
])
},
computed: {
...mapState({
storetodo: state => state.todos.todo
}),
todo: {
get () { return this.storetodo},
set (value) { this.updateTodo(value) }
}
}
I would like to skip the extra step and define the getter, setter directly within mapState.
Why would I want to do this?
The normal approach would be use mapMutations/mapActions & mapState/mapGetters
without the computed get/set combination that I have illustrated above and to reference the mutation directly in the HTML:
<input v-model='todo' v-on:keyup.stop='updateTodo($event.target.value)' />
The getter/setter version allows me to simply write:
<input v-model='todo' />
You can't use a getter/setter format in the mapState
what you can try is directly return the state in your get() and remove mapState from the computed property
computed: {
todo: {
get () { return this.$store.state.todos.todo},
set (value) { this.updateTodo(value) }
}
}
Here is a related but not same JsFiddle example
This is my current workaround. Copied from my personal working project
// in some utils/vuex.js file
export const mapSetter = (state, setters = {}) => (
Object.keys(state).reduce((acc, stateName) => {
acc[stateName] = {
get: state[stateName],
};
// check if setter exists
if (setters[stateName]) {
acc[stateName].set = setters[stateName];
}
return acc;
}, {})
);
In your component.vue file
import { mapSetter } from 'path/to/utils/vuex.js';
export default {
name: 'ComponentName',
computed: {
...mapSetter(
mapState({
result: ({ ITEMS }) => ITEMS.result,
total: ({ ITEMS }) => ITEMS.total,
current: ({ ITEMS }) => ITEMS.page,
limit: ({ ITEMS }) => ITEMS.limit,
}),
{
limit(payload) {
this.$store.dispatch({ type: TYPES.SET_LIMIT, payload });
},
},
)
},
}
now you can use the v-model bindings. l
Another way of approaching that is using store mutations like below:
//in your component js file:
this.$store.commit('setStoretodo', storetodo)
Assuming you define setStoretodo in mutations of your vuex store instance (which is something recommended to have anyways):
//in your vuex store js file:
state:{...},
actions: {...}
...
mutations: {
setStoretodo(state, val){
state.storetodo = val
},
...
}
...
That keeps the property reactive as mapState will grab the updated value and it will be rendered automatically.
Surely, that's not as cool as just writing this.storetodo = newValue, but maybe someone will find that helpful as well.