Prop passed to child component is undefined in created method - vue.js

I am using Vue.js 2.
I have a problem with passing value to the child component as a prop. I am trying to pass card to card-component.
In card-component I can access the prop in the Card goes here {{card}} section.
However when I try to access it in created or mounted methods it's undefined.
Parent:
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<card-component :card="place.card"></card-component>
</div>
</div>
</div>
</template>
<script>
import CostComponent from './CostComponent';
import CardComponent from './CardComponent';
export default {
components: {
CostComponent, CardComponent
},
props: ['id'],
data() {
return {
place: []
}
},
created() {
axios.get('/api/places/' + this.id)
.then(response => this.place = response.data);
}
}
</script>
Child:
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<ul class="list-unstyled">
Card goes here {{card}}
</ul>
</div>
</div>
</div>
</template>
<script>
import CardItemComponent from './CardItemComponent';
export default {
components: {
CardItemComponent
},
props: ['card'],
created() {
console.log(this.card); // undefined
},
mounted() {
console.log(this.card); // undefined
},
}
</script>
I did a lot of googling but none of the solutions I found have fixed my issue.

This is purely a timing issue. Here's what happens...
Your parent component is created. At this time it has an empty array assigned to place (this is also a problem but I'll get to that later). An async request is started
Your parent component creates a CardComponent instance via its template
<card-component :card="place.card"></card-component>
at this stage, place is still an empty array, therefore place.card is undefined
3. The CardComponent created hook runs, logging undefined
4. The CardComponent is mounted and its mounted hook runs (same logging result as created)
5. Your parent component is mounted
6. At some point after this, the async request resolves and changes place from an empty array to an object, presumably with a card property.
7. The new card property is passed down into your CardComponent and it reactively updates the displayed {{ card }} value in its template.
If you want to catch when the card prop data changes, you can use the beforeUpdate hook
beforeUpdate () {
console.log(this.card)
}
Demo
Vue.component('CardComponent', {
template: '<pre>card = {{ card }}</pre>',
props: ['card'],
created () {
console.log('created:', this.card)
},
mounted () {
console.log('mounted:', this.card)
},
beforeUpdate () {
console.log('beforeUpdate:', this.card)
}
})
new Vue({
el: '#app',
data: {
place: {}
},
created () {
setTimeout(() => {
this.place = { card: 'Ace of Spades' }
}, 2000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<card-component :card="place.card" />
</div>
See https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
If place is meant to be an object, you should not be initialising it as an array. Also, if your CardComponent relies on data being present, you may want to conditionally render it.
For example
data () {
return { place: null }
}
and
<card-component v-if="place" :card="place.card"></card-component>
then CardComponent will only be created and mounted after place has data.

Make sure you have props: true in the router file. It is a simple solution but many of us forget this.
{
path: '/path-to',
name: 'Name To',
component: Component,
props: true
}

Related

error Unexpected mutation of "todo" prop in vue.js (I'm using vue3)

I'm making a todo app in vue.js which has a component TodoItem
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo"],
methods: {
markCompleted() {
this.todo.completed = true
},
},
};
</script>
todo prop that I'm passing:
{
id:1,
task:'todo 1',
completed:false
}
but it is throwing an error error Unexpected mutation of "todo" prop
Method 1 (Vue 2.3.0+) - From your parent component, you can pass prop with sync modifier
Parent Component
<TodoItem v-for="todo in todoList" :key="todo.id" todo_prop.sync="todo">
Child Component
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo_prop"],
data() {
return {
todo: this.todo_prop
}
},
methods: {
markCompleted() {
this.todo.completed = true
},
},
};
</script>
Method 2 - Pass props from parent component without sync modifier and emit an event when the value changed. For this method, everything else is similar as well. Just need to emit an event when the todo item changed to completed.
The code is untested. Apologies if anything does not work.
What happen ? : Mutating a prop locally is now considered an anti-pattern, e.g. declaring a prop and then setting this.myProp = 'someOtherValue' in the component. Due to the new rendering mechanism, whenever the parent component re-renders, the child component’s local changes will be overwritten.
Solution : You can storage it as local data.
export default {
name: "TodoItem",
props: ["todo"],
data() {
return {
todoLocal: this.todo,
};
},
methods: {
markComplete() {
this.todoLocal.completed = !this.todoLocal.completed;
},
},
};
For me to fix this problem I store props in todos data im watching brad vue tutorials and i get this error this is my actual codes and its working.
<template>
<div class="todo-item" v-bind:class="{ 'is-complete': todo.completed }">
<p>
<input
type="checkbox"
v-on:change="markComplete(todo.completed)"
v-bind:checked="todo.completed"
/>
{{ todo.title }}
<!-- <button #click="$emit('del-todo', todo.id)" class="del">x</button> -->
</p>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props: ['todo'],
data() {
return {
todos: this.todo,
}
},
methods: {
markComplete(isComplete) {
this.todos.completed = !isComplete
},
},
}
</script>
<style scoped>
.todo-item {
background: #f4f4f4;
padding: 10px;
border-bottom: 1px #ccc dotted;
}
.is-complete {
text-decoration: line-through;
}
.del {
background: #ff0000;
color: #fff;
border: none;
padding: 5px 9px;
border-radius: 50%;
cursor: pointer;
float: right;
}
</style>
One of the core principles of VueJS is that child components never mutate a prop.
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around.
If you wish to have the child component update todo.completed, you have two choices:
Use .sync modifier (Recommended)
This approach will require a bit of change to your props. You can read more about it here.
Parent component
<template>
<div>
...
<todo-item :task="nextTodo.task" :completed.sync="nextTodo.completed"/>
</div>
</template>
Child component
<template>
<div class="todo-item" v-bind:class="{'is-completed':completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["task", "completed"],
methods: {
markCompleted() {
this.$emit('update:completed', true)
},
},
};
</script>
Use a custom event
Vue allows you set up listeners in your parent for events that the child will emit. Your child component can use this mechanism to ask the parent to change things. In fact, the above .sync modifier is doing exactly this behind the scenes.
Parent component
<template>
<div>
...
<todo-item :todo="nextTodo" #set-completed="$value => { nextTodo.completed = $value }/>
</div>
</template>
Child component
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo"],
methods: {
markCompleted() {
this.$emit('set-completed', true)
},
},
};
</script>
You can't change a prop from inside a component - they are meant to be set by the parent only. It's a one-directional communication path.
You can try one of two things - either move your logic for detecting a todo has been completed to the parent, or feed the prop into a new variable in the data() lifecycle hook (this will only happen when the component is loaded for the first time, so you won't be able to update from outside the component, if that's important for your use case).
The canonical way to achieve n-deep prop binding in Vue 3 is to wrap your prop with a simple computed property. This is an example of a component that will communicate changes to it's selected property to it's parent--who is ultimately responsible for storing the state.
<template>
<!-- In this example my-child-component also has a "selected" prop -->
<my-child-component v-model:selected="syncSelectedId" />
</template>
<script lang="ts">
export default defineComponent({
components: { MyChildComponent },
props: {
selected: {
type: String,
required: true,
}
},
emits: ['update:selected'],
setup(props, context) {
const syncSelectedId = computed<string>({
get() {
return props.selected;
},
set(newVal: string) {
context.emit('update:selected', newVal);
},
});
return {
syncSelectedId,
}
}
});
So to re-iterate: With this strategy the highest level parent is the holder of the state. The code above assumes that there is a parent component in the hierarchy (so this component is just a "middle-man").
Then my-child-component can simply emit its own update:selected event to cause the state to change. That child will be updated appropriately through it's prop after the emit event causes the parent chain to propagate that change up (through emits) and then back down the component hierarchy (through props).
If you wanted to you could modify the code above to make it the "owner" of the state:
<template>
<my-child-component v-model:selected="selected" />
</template>
<script lang="ts">
export default defineComponent({
components: { MyChildComponent },
setup(props, context) {
const selected = ref('');
return {
selected,
}
}
});
And now of course you won't run into the "Unexpected mutation of X prop" error.
Another option is to have a prop that serves as a "default value" for a given state:
<template>
<my-child-component v-model:selected="selected" />
</template>
<script lang="ts">
export default defineComponent({
components: { MyChildComponent },
props: {
defaultSelected: {
type: String,
required: false,
default: ''
}
},
setup(props, context) {
const selected = ref(props.defaultSelected);
return {
selected,
}
}
});
And in this code above keep in mind that selected will NOT change if defaultSelected changes after the component has been initialized.
And lastly it's worth noting that you could write more sophisticated code to detect if a property is supplied--and if not use an internal state variable to store the value. I use this pattern for re-usable components that could be embedded in places where the parent wants to control the state OR in places where the parent is happy to delegate the storage of the state to the child:
<template>
<!-- In this example my-child-component also has a "selected" prop -->
<my-child-component v-model:selected="syncSelectedId" />
</template>
<script lang="ts">
export default defineComponent({
components: { MyChildComponent },
props: {
selected: {
type: String,
required: false,
default: null // Important: parent MUST pass non-null value if it wants to control state
}
},
emits: ['update:selected'],
setup(props, context) {
// This is state storage used if prop.selected is not provided
const _selected = ref('');
const syncSelectedId = computed<string>({
get() {
return props.selected === null ? _selected.value : props.selected;
},
set(newVal: string) {
if (props.selected !== null) {
// Using prop.selected as the driving model...
if (newVal !== props.selected) {
// We need to set to empty string (never null)
context.emit('update:selectedId', (newVal == null ? '' : newVal));
}
} else { // Storing selection state with _selectedId
if (newVal !== _selected.value) {
_selected.value = newVal == null ? '' : newVal;
context.emit('update:selected', _selected);
}
}
},
});
return {
syncSelectedId,
}
}
});
This last example is tricky... it gives special meaning to null and requires that you be very mindful of potential values of your state. In my example empty string is my representation for "no selection" and null is used as a flag for "no parent model of this state".
Mainly, property mutation is now deprecated and parent properties are overwritten when the parent component renders its DOM.
Here's the official documentation about it. We can still achieve this in multiple possible ways. Through a data property, a computed property, and component events.
When we want to pass this value back to the parent component as well as the nested child component of the current child component, using a data property would be useful as shown in the following example.
Example:
Calling your child component from the parent component like this.
Parent component:
<template>
<TodoItem :todoParent="todo" />
</template>
<script>
export default {
data() {
return {
todo: {
id:1,
task:'todo 1',
completed:false
}
};
}
}
</script>
Child component:
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todoParent"],
data() {
return {
todo: this.todoParent,
};
},
methods: {
markCompleted() {
this.todo.completed = true
},
},
};
</script>
Even you can pass this property to the nested child component and it won't give this error/warning.
Other use cases when you only need this property sync between parent and child component. It can be achieved using the sync modifier from Vue. v-model can also be useful. Many other examples are available in this question thread.
Example2: using component events.
We can emit the event from the child component as below.
Parent component:
<template>
<TodoItem :todo="todo" #markCompletedParent="markCompleted" />
</template>
<script>
export default {
data() {
return {
todo: {
id:1,
task:'todo 1',
completed:false
}
};
},
methods: {
markCompleted() {
this.todo.completed = true
},
}
}
</script>
Child component:
<template>
<div class="todo-item" v-bind:class="{'is-completed':todo.completed}">
<p>
<input type="checkbox" #change="markCompleted" />
{{todo.task}}
<button class="del">x</button>
</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo"],
methods: {
markCompleted() {
this.$emit('markCompletedParent', true)
},
}
};
</script>
While you can still custom-bind events to handle this, .sync property extensions are considered deprecated. In Vue3 (at least) you can and usually should use the v-model:property declaration, similar to how you bind the property to the actual input. You just need to bind the inner input with :value and have it emit a matching update:property
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
#input="$emit('update:modelValue', $event.target.value)"
/>
</template>
And use thusly:
<CustomInput v-model="searchText" />

Why is the activated lifecycle hook not called on first visit

I have a problem where a component within a router-view that is being kept alive does not call its activated lifecycle hook when first created. The created and mounted lifecycle hooks are being called. On a second visit, the activated hook is being called.
The scenario is quite complicated as there is a bit of nesting and slot using involved.
I've tried to create a minimal example which you can find below, or a bit more detailed on https://codesandbox.io/s/251k1pq9n.
Unfortunately, it is quite large and still not as complicated as the real code which I unfortunately cannot share.
Worse, I failed to reproduce the actual problem in my minimal example. Here, the created, mounted, and activated lifecycle hooks are all called when first visiting SlotExample.
In my real code, only the created and mounted, lifecycle hooks are called on the first visit, the activated hook is called on subsequent visits. Interestingly, all lifecycle hooks are called as expected for SlotParent.
The real code involves more nesting and makes use of slots to use layout components.
My code is using Vue 2.5.16 and Vue-Router 3.0.1 but it also doesn't work as expected in Due 2.6.7 and Vue-Router 3.0.2. I am also using Vuetify and Vue-Head but don't think think this has anything to do with my problem.
index.js.
Does anyone have an idea what I could have been doing wrong. I actually suspect a bug in vue-router
when using multiple nested slots and keep-alive but cannot reproduce.
index.js
import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App.vue";
import Start from "./Start.vue";
import SlotExample from "./SlotExample.vue";
const routes = [
{
path: "/start",
component: Start
},
{
path: "/slotExample/:id",
component: SlotExample,
props: true
}
];
const router = new VueRouter({ routes });
Vue.use(VueRouter);
new Vue({
render: h => h(App),
router,
components: { App }
}).$mount("#app");
App.vue
<template>
<div id="app">
<div>
<keep-alive><router-view/></keep-alive>
</div>
</div>
</template>
SlotExample.vue
<template>
<div>
<h1>Slot Example</h1>
<router-link to="/start"><a>start</a></router-link>
<router-link to="/slotExample/123">
<a>slotExample 123</a>
</router-link>
<slot-parent :id="id">
<slot-child
slot-scope="user"
:firstName="user.firstName"
:lastName="user.lastName"/>
</slot-parent>
</div>
</template>
<script>
import SlotParent from "./SlotParent.vue";
import SlotChild from "./SlotChild.vue";
export default {
name: "slotExample",
components: { SlotParent, SlotChild },
props: {
id: {
type: String,
required: true
}
}
};
</script>
SlotParent.vue
<template>
<div>
<div slot="header"><h1>SlotParent</h1></div>
<div slot="content-area">
<slot :firstName="firstName" :lastName="lastName" />
</div>
</div>
</template>
<script>
export default {
name: "slotParent",
props: {
id: {
type: String,
required: true
}
},
computed: {
firstName() {
if (this.id === "123") {
return "John";
} else {
return "Jane";
}
},
lastName() {
return "Doe";
}
}
};
</script>
SlotChild.vue
<template>
<div>
<h2>SlotChild</h2>
<p>{{ firstName }} {{ lastName }}</p>
</div>
</template>
<script>
export default {
name: "slotChild",
props: {
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
}
},
created() {
console.log("slotChild created");
},
mounted() {
console.log("slotChild mounted");
},
activated() {
console.log("slotChild activated");
}
};
</script>
I think you need to put SlotChild within keep-alive block.
Take a look at vue js doc about activated hook

Vuejs props use in the client component

Hi I'm trying to pass a value to a child component as props and trying to use this value in child's created hook but it's not getting set. See example below,
<!-- Parent component -->
<template>
<div>
<details
:customer_id = this.customer_id
:foo = "bar" />
</div>
</template>
<script>
import CustomerDetail from './CustomerDetails';
export default {
name: 'Customer',
data: function() {
return {
customer_id: '',
}
components: {
'detail': CustomerDetail
},
created: function() {
var id = this.$route.params.id;
this.customer_id = id;
} // created
}
</script>
<!-- Details component -->
<template>
<div>
<h1>{{foo}}</h1>
</div>
</template>
<script>
export default {
name: 'CustomerDetail',
props: ['customer_id', 'foo']
created: function() {
console.log(this.customer_id); <!-- -->
} // created
}
</script>
As shown in above code, when child component is rendered, may times the customer_id in created() hook of child component is undefined. It shows up occasionally if hotloading happens on the same view. How do I make sure that this value always available. In this case I want to do server call to get customer details. At the same time {{foo}} correctly show value 'bar'. What am I missing? Any help is appreciated.
Registered child components actually have direct access to the route params, since you are using Dynamic Route Matching, you can simply get the dynamic params via $routes.params.* from the child components themselves.
const Customer = {
template: `
<div>
<h3>Customer ID: {{$route.params.id}}</h3>
</div>
`
}
const routes = [
{ path: '/customers/:id', component: Customer }
];
new Vue({
el: '#app',
router: new VueRouter({
routes
}),
data() {
return {
bar: 'Doh!',
//customer_id: '',
}
},
components: {
CustomerDetails: {
template: `
<div>
<h1>Value from parent: <em>{{foo}}</em></h1>
</div>
`,
props: ['foo']
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.2/vue-router.min.js"></script>
<div id="app">
<div>
<label>View profile:</label>
<router-link to="/customers/john">John</router-link>
<router-link to="/customers/doe">Doe</router-link>
<router-view></router-view>
<div>
<customer-details :foo="bar"></customer-details>
</div>

VueJS Passing computed data as a prop returns undefined

I've tried and tried, but i can't figure it out the problem. From what I could read elsewhere, the variable passed to the child component gets sent as undefined before the data is available in the parent.
Please see here for reference:
the code in codesandbox
<template>
<div id="app">
<child :parentData="data.message"/>
</div>
</template>
<script>
import Child from "./components/Child";
export default {
name: "App",
components: {
Child
},
computed: {
quote() { return 'Better late than never' }
},
data() {
return {
data: { message: this.quote } ,
thisWorks: { message: "You can see this message if you replace what is passed to the child" }
};
}
};
</script>
Then in the child:
<template>
<div>
<h1>I am the Child Component</h1>
<h2> {{ parentData }}</h2>
</div>
</template>
<script>
export default {
name: "Child",
props: {
parentData: { type: String, default: "I don't have parent data" },
},
};
</script>
The answer is, you cannot access the value of this.quote because at the moment the data objectis creating, the computed object actually does not exist.
This is an alternative, we will use the created() lifecycle hook to update the value of data object:
created(){
this.data = {
message: this.quote
}
},
You don't need to change any things, just adding those line of codes is enough.
I've already tested those codes in your CodeSandbox project and it works like a charm.
Hopefully it helps!

vue reload child component

I'm using vue, version 2.5.8
I want to reload child component's, or reload parent and then force children components to reload.
I was trying to use this.$forceUpdate() but this is not working.
Do You have any idea how to do this?
Use a :key for the component and reset the key.
See https://michaelnthiessen.com/force-re-render/
Add key to child component, then update the key in parent. Child component will be re-created.
<childComponent :key="childKey"/>
If the children are dynamically created by a v-for or something, you could clear the array and re-assign it, and the children would all be re-created.
To simply have existing components respond to a signal, you want to pass an event bus as a prop, then emit an event to which they will respond. The normal direction of events is up, but it is sometimes appropriate to have them go down.
new Vue({
el: '#app',
data: {
bus: new Vue()
},
components: {
child: {
template: '#child-template',
props: ['bus'],
data() {
return {
value: 0
};
},
methods: {
increment() {
this.value += 1;
},
reset() {
this.value = 0;
}
},
created() {
this.bus.$on('reset', this.reset);
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<button #click="() => bus.$emit('reset')">Reset</button>
</div>
<template id="child-template">
<div>
{{value}} <button #click="increment">More!</button>
</div>
</template>
I'm using directive v-if which is responsible for conditional rendering. It only affects reloading HTML <template> part. Sections created(), computed are not reloaded. As I understand after framework load components reloading it is not possible. We can only re render a <template>.
Rerender example.
I have a Child.vue component code:
<template>
<div v-if="show">
Child content to render
{{ callDuringReRender() }}
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
}
,
methods: {
showComponent() {
this.show = true
},
hideComponent() {
this.show = false
},
callDuringReRender() {
console.log("function recall during rendering")
}
}
}
</script>
In my Parent.vue component I can call child methods and using it's v-if to force the child rerender
<template>
<div>
<input type="button"
value="ReRender the child"
#click="reRenderChildComponent"/>
<child ref="childComponent"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
methods: {
reRenderChildComponent(){
this.$refs.childComponent.hideComponent();
this.$refs.childComponent.showComponent()
}
},
components: {
Child
}
}
</script>
After clicking a button in console You will notice message "function recall during rendering" informing You that component was rerendered.
This example is from the link that #sureshvv shared
import Vue from 'vue';
Vue.forceUpdate();
// Using the component instance
export default {
methods: {
methodThatForcesUpdate() {
// ...
this.$forceUpdate(); // Notice we have to use a $ here
// ...
}
}
}
I've found that when you want the child component to refresh you either need the passed property to be output in the template somewhere or be accessed by a computed property.
<!-- ParentComponent -->
<template>
<child-component :user="userFromParent"></child-component>
</template>
<!-- ChildComponent -->
<template>
<!-- 'updated' fires if property 'user' is used in the child template -->
<p>user: {{ user.name }}
</template>
<script>
export default {
props: {'user'},
data() { return {}; }
computed: {
// Or use a computed property if only accessing it in the script
getUser() {
return this.user;
}
}
}
</script>