Vue: Set child-component data with props values not working - javascript

Simply, I have two components:
Parent component which passes a prop object called "profile"
Child component which receives the profile prop
The profile value is an object like this:
{
name: "Something",
email: "some#thing.com"
}
What happens?
The child component receives perfectly the profile value in the template, but it seems impossible to retrieve and set it to the component data.
What is the goal?
I want to initialise the value "email" with the profile email prop.
What did I expect?
export default {
props: ["profile"],
data() {
return {
email: this.profile.email
}
}
}
UPDATE
I haven't specified that email is a data value used as model.
I have just tried to remove it and simply print the value of email in the template and it doesn't work as well.
<!-- PARENT COMPONENT -->
<template>
<dialog-settings ref="dialogSettings" :profile="profile"></dialog-settings>
</template>
<script>
import Auth from "../services/apis/auth";
import DialogSettings from "../components/dialog-settings";
export default {
name: "app",
components: {
"dialog-settings": DialogSettings
},
beforeCreate() {
Auth.checkToken()
.then(profile => {
this.profile = profile;
})
.catch(err => {
});
},
data() {
return {
title: "App",
drawer: true,
profile: {},
navItems: []
};
}
}
</script>
<!-- CHILD COMPONENT -->
<template>
{{profile}} <!-- All the fields are passed and available (e.g. profile.email)-->
{{email}} <!-- Email is not defined -->
</template>
<script>
import Auth from "../services/apis/auth";
import DialogSettings from "../components/dialog-settings";
export default {
name: "dialog-settings",
props: ["profile"],
data() {
return {
email: this.profile.email
}
}
}
</script>
UPDATE 2
I have tried several things and I think that the problem is the asynchronous call to the API in the beforeCreate().

your child component email property should be a computed value
<!-- CHILD COMPONENT -->
<template>
<div>
{{profile}} <!-- All the fields are passed and available (e.g. profile.email)-->
{{email}} <!-- Email is not defined -->
</div>
</template>
<script>
import Auth from "../services/apis/auth";
import DialogSettings from "../components/dialog-settings";
export default {
name: "dialog-settings",
props: ["profile"],
data() {
return {
}
},
computed: {
email () {
return this.profile ? this.profile.email : 'no email yet'
}
}
}
</script>
That's because parent component property is set after rendering child component.
"Data" is not reactive, it's set once when component is created. Prop 'profile" is reactive so first when you render component you should see {} and after response from Auth is set.
If you still want to keep it in data, you could display child component like that:
<dialog-settings ref="dialogSettings" :profile="profile" v-if="profile.email"></dialog-settings>
But i wouldn't recommend that!

Related

Reactive property is not propagating to component in Vue 3 app

I have a Vue 3 app. I am trying to setup a store for state management. In this app, I have the following files:
app.vue
component.vue
main.js
store.js
These files include the following:
store.js
import { reactive } from 'vue';
const myStore = reactive({
selectedItem: null
});
export default myStore;
main.js
import { createApp } from 'vue';
import App from './app.vue';
import myStore from './store';
const myApp = createApp(App);
myApp.config.globalProperties.$store = myStore;
myApp.mount('#app');
component.vue
<template>
<div>
<div v-if="item">You have selected an item</div>
<div v-else>Please select an item</div>
<button class="btn btn-primary" #click="generateItem">Generate Item</button>
</div>
</template>
<script>
export default {
props: {
item: Object
},
watch: {
item: function(newValue, oldValue) {
alert('The item was updated.');
}
},
methods: {
generateItem() {
const item = {
id:0,
name: 'Some random name'
};
this.$emit('itemSelected', item);
}
}
}
</script>
app.vue
<template>
<component :item="selectedItem" #item-selected="onItemSelected" />
</template>
<script>
import Component form './component.vue';
export default {
components: {
'component': Component
},
data() {
return {
...this.$store
}
},
methods: {
onItemSelected(item) {
console.log('onItemSelected: ');
console.log(item);
this.$store.selectedItem = item;
}
}
}
</script>
The idea is that the app manages state via a reactive object. The object is passed into the component via a property. The component can then update the value of the object when a user clicks the "Generate Item" button.
I can see that the selectedValue is successfully passed down as a property. I have confirmed this by manually setting selectedValue to a dummy value to test. I can also see that the onItemSelected event handler works as expected. This means that events are successfully flowing up. However, when the selectedItem is updated in the event handler, the updated value is not getting passed back down to the component.
What am I doing wrong?
$store.selectedItem stops being reactive here, because it's read once in data:
data() {
return {
...this.$store
}
}
In order for it to stay reactive, it should be either converted to a ref:
data() {
return {
selectedItem: toRef(this.$store, 'selectedItem')
}
}
Or be a computed:
computed: {
selectedItem() {
return this.$store.selectedItem
}
}

component not handling empty prop

I have a component data.table.vue and other components for eg abc.vue and xyz.vue.
inside this data.table.vue, a paragraph to render depending on the prop received by the it.. However, not both my components abc.vue and xyz.vue will send props.. only abc.vue needs to send props.. for eg:
in abc.vue:
<template>
<data-table
:isShown=true
<data-table>
</template>
and in xyz.vue no props
<template>
<data-table
</data-table>
</template>
and in data.table.vue
<p v-if="isShown"> hello world </p>
but I want this paragraph to be always shown for xyz component..
and only for abc.vue, i want this paragraph to render according to the props isShown.. However, even in xyz.vue , its being rendered depending on the props sent in abc.vue..
Please help ..
You can set a default prop like this.
export default {
props: {
isShown: {
type: Object,
default: true
}
}
}
Default will be taken when no props are passed.
For Vue3 with the composite API you can set default props like this:
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
isShown: {
type: boolean,
default: true
},
},
setup() {},
})
</script>
And with the script setup
<script setup lang="ts">
const props = withDefaults(
defineProps<{
isShown: boolean
}>(),
{
isShown: true
}
)
</script>

How to change vue-cookie-law default Props

I am trying to change buttonText default value of the component vue-cookie-law with Props.
I can change the default value directly from the node_modules plugin source code, but I would like to change it from a Vue Single File Component.
vue-cookie-law - https://www.npmjs.com/package/vue-cookie-law
prop default type
buttonText: 'Got It!'
Since I haven't used Props before, I've been trying few things, below is my CookieLaw.vue component
<template>
<footer>
<cookie-law theme="base">
<div slot="message">
We use cookies to enhance your experience. By continuing to visit our site you agree to our use of cookies.
<router-link to="terms_and_conditions">View Policy</router-link>
</div>
</cookie-law>
</footer>
</template>
<script>
import CookieLaw from "vue-cookie-law";
export default {
props: {
buttonText: {
default: "Agree"
}
},
components: { CookieLaw }
};
</script>
The props are not changing the default of buttonText.
buttonText is one of the default props for the vue-cookie-law component as you know ... not the parent component (the one that you import it on ) so you have to bind them to the component it self :
<cookie-law theme="base" buttonText="Agree">
...
</cookie-law>
Or bind a dynamic value :
<script>
import CookieLaw from "vue-cookie-law";
export default {
data() {
return {
text: 'Agree'
}
}
components: {
CookieLaw
}
}; <
</script>
<cookie-law theme="base" :buttonText="text">
...
</cookie-law>

How to set initial values for data from vuex?

My goal is to create an 'edit account' form such that a user can modify their account data. I want to present the account data in a form that is already filled with the users data i.e username, email, address ...
The user can then modify the data in the form and submit this form that will update their user information.
I am using v-model to bind the form input to an object called accountInfo in my data, that looks like this:
data() {
return {
accountInfo: {
firstName: ''
}
}
}
And here is an example of a form input in my template:
<input v-model.trim="accountInfo.firstName" type="text" class="form-control" id="first-name" />
The values for the key's in the object are currently empty strings but I would like the values to come from an object called userProfile that is a state property in vuex.
In my 'edit account' component I am mapping the vuex state by importing:
import { mapState } from "vuex";
then using the following in a computed property
computed: {
...mapState(["userProfile"])
}
What I would like to do is instead of having empty strings as the values of accountInfo, assign them values from the userProfile computed property mapped from vuex, like so:
data() {
return {
accountInfo: {
firstName: this.userProfile.fristName,
}
}
}
This will provide the desired initial data for my form but unfortunately this doesn't work, presumably because data is rendered earlier on in the life cycle than computed properties.
Full code:
EditAccount.vue
<template>
<div class="container-fluid">
<form id="sign_up_form" #submit.prevent>
<div class="form-row">
<div class="form-group col-md-6">
<input v-model.trim="signupForm.firstName" type="text" class="form-control" id="first_name" />
</div>
</div>
</form>
</div>
</template>
<script>
import { mapState } from "vuex";
import SideBar from "../common/SideBar.vue";
export default {
name: "EditAccount",
computed: {
...mapState(["userProfile"])
},
data() {
return {
accountInfo: {
firstName: this.userProfile.firstName
}
};
}
};
</script>
store.js:
export const store = new Vuex.Store({
state: {
userProfile: {firstName: "Oamar", lastName: "Kanji"}
}
});
You were right, computeds are evaluated after the initial data function is called.
Quick fix
In the comments, #Jacob Goh mentioned the following:
$store should be ready before data function is called. Therefore, firstName: this.$store.state.userProfile.firstName should just work.
export default {
name: 'EditAccount',
data() {
return {
accountInfo: {
firstName: this.$store.state.userProfile.firstName
}
}
}
};
Really need computeds?
See #bottomsnap's answer, where setting the initial value can be done in the mounted lifecycle hook.
With your code, it would look like this:
import { mapState } from 'vuex';
export default {
name: 'EditAccount',
computed: {
...mapState(['userProfile'])
},
data() {
return {
accountInfo: {
firstName: ''
}
}
}
mounted() {
this.accountInfo.firstName = this.userProfile.firstName;
}
};
Though it may render once without the value, and re-render after being mounted.
Container versus presentation
I explain Vue's communication channels in another answer, but here's a simple example of what you could do.
Treat the Form component as presentation logic, so it doesn't need to know about the store, instead receiving the profile data as a prop.
export default {
props: {
profile: {
type: Object,
},
},
data() {
return {
accountInfo: {
firstName: this.profile.firstName
}
};
}
}
Then, let the parent handle the business logic, so fetching the information from the store, triggering the actions, etc.
<template>
<EditAccount :profile="userProfile" :submit="saveUserProfile"/>
</template>
<script>
import { mapState, mapActions } from "vuex";
export default {
components: { EditAccount },
computed: mapState(['userProfile']),
methods: mapActions(['saveUserProfile'])
}
</script>
While Jacob is not wrong saying that the store is ready, and that this.$store.state.userProfile.firstName will work, I feel this is more a patch around a design problem that can easily be solved with the solution above.
Bind your input with v-model as you were:
<div id="app">
<input type="text" v-model="firstName">
</div>
Use the mounted lifecycle hook to set the initial value:
import Vue from 'vue';
import { mapGetters } from 'vuex';
new Vue({
el: "#app",
data: {
firstName: null
},
computed: {
...mapGetters(["getFirstName"])
},
mounted() {
this.firstName = this.getFirstName
}
})

How to design a store in Vuex to handle clicks in nested, custom components?

I'm trying to design a store to manage the events of my Vuex application. This far, I have the following.
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = { dataRows: [], activeDataRow: {} };
const mutations = {
UPDATE_DATA(state, data) { state.dataRows = data; state.activeDataRow = {}; },
};
export default new Vuex.Store({ state, mutations });
I'm going to have a number of list items that are supposed to change the value of the data in the store when clicked. The design of the root component App and the menu bar Navigation is as follows (there will be a bunch of actions in the end so I've collected them in the file actions.js).
<template>
<div id="app">
<navigation></navigation>
</div>
</template>
<script>
import navigation from "./navigation.vue"
export default { components: { navigation } }
</script>
<template>
<div id="nav-bar">
<ul>
<li onclick="console.log('Clickaroo... ');">Plain JS</li>
<li #click="updateData">Action Vuex</li>
</ul>
</div>
</template>
<script>
import { updateData } from "../vuex_app/actions";
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
}
}
</script>
Clicking on the first list item shows the output in the console. However, when clicking on the second one, there's nothing happening, so I'm pretty sure that the event isn't dispatched at all. I also see following error when the page's being rendered:
Property or method "updateData" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
I'm very new to Vuex so I'm only speculating. Do I need to put in reference to the updateData action in the store, alongside with state and mutations? How do I do that? What/where's the "data option" that the error message talks about? Isn't it my components state and it's properties?
Why the error
You are getting the error, because when you have <li #click="updateData"> in the template, it looks for a method updateData in the vue component which it does not find, so it throws the error. To resolve this, you need to add corresponding methods in the vue component like following:
<script>
import { updateData } from "../vuex_app/actions";
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
},
methods:{
updateData: () => this.$store.dispatch("updateData")
}
}
</script>
What this.$store.dispatch("updateData") is doing is calling your vuex actions as documented here.
What/where's the "data option"
You don't have any data properties defined, data properties for a vue component can be used, if you want to use that only in that component. If you have data which needs to be accessed across multiple components, you can use vuex state as I believe you are doing.
Following is the way to have data properties for a vue component:
<script>
import { updateData } from "../vuex_app/actions";
export default {
date: {
return {
data1 : 'data 1',
data2 : {
nesteddata: 'data 2'
}
}
}
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
},
methods:{
updateData: () => this.$store.dispatch("updateData")
}
}
</script>
You can use these data properties in the views, have computed properies based on it, or create watchers on it and many more.

Categories

Resources