I'm using VueJS and I need to pass Firebase Authentication user data (JSON) from the component App.vue to another component called Details.vue to display the name of that logged in user.
App.vue:
export default {
name: 'app',
beforeCreate: function () {
Firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.user = user
// user is logged in now -> pass data to App.vue component here
[...]
What is a good way to do this?
(I'm new to VueJS and tried a couple of suggested answers like using "ref" <add ref="details"></add> - unfortunately none of them worked for me)
The long-run solution is Vuex.
If the user is the only global property you want to have, you can create a data property at the root and access from any component using this.$root.propName.
Example:
// probably main.js file
new Vue({
el: "#app",
data: {user: null}, // added this
components: { App },
template: "<App/>"
});
App.js:
export default {
name: 'app',
beforeCreate: function () {
Firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.$root.user = user; // set to this.$root.user instead of this.user
In any other component:
export default {
name: 'something',
methods: {
printUser() { console.log(this.$root.user); }
},
// ...
That's a quick solution. Don't abuse the $root data. Having user is like having a global variable. The more code uses it, the harder it becomes to debug. Vuex handles that by having all changes concentrated inside mutations. In summary: if you find yourself needing more $root properties, go straight to Vuex.
Related
So I have a vuejs project and want to use some variable (globally) in any of my components (Variable will not change its value).
I created a variable called STEPS in somefile.js and imported it from components where I want to use.
// somefile.js
export const STEPS = {
PROJECT: 'project',
USER: 'user',
SUMMARY: 'summary',
}
// maybe freeze object here because value will not be changed
// component1.vue
import { STEPS } from 'somefile.js'
export default {
methods: {
someMethod(value) {
if (value === STEPS.PROJECT) {
// do something
}
}
}
}
// component2.vue
import { STEPS } from 'somefile.js'
export default {
methods: {
someMethod(value) {
if (value === STEPS.USER) {
// do something
}
}
}
}
So this actually works? I don't get any errors or anything. But I'm just wondering .. I am not sure if it's okay to use it like this? Just making a .js file and import it anywhere in your code and use it?
So..
I found 2 ways how people use global variables in vuejs.
using instance properties
https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html#Base-Example
Vue.prototype.$STEPS = {
PROJECT: 'project',
USER: 'user',
SUMMARY: 'summary',
}
created() {
console.log(this.$STEPS)
}
Using mixins
https://v2.vuejs.org/v2/guide/mixins.html
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
So my question is is it okay to use how I used global variable? Just making a variable in javascript file and importing it..
Or should I change it to using instance properties or mixins?
Yes, the way you do it is totally fine. If you want to manage your data centrally where is can be accessed by all components (and can even be changed across all of them simultaneously), you could also have a look at Vuex. Here is also a great (but maybe outdated) tutorial on Vuex.
I'm trying to write a Vue plugin that's a simple abstraction to manage auth state across my app. This will need to access other Vue plugins, namely vuex, vue-router and vue-apollo (at the moment).
I tried extending Vue.prototype but when I try to access the plugin's properties how I would normally - eg. this.$apollo - I get the scope of the object, and therefore an undefined error. I also tried adding vm = this and using vm.$apollo, but this only moves the scope out further, but not to the Vue object - I guess this is because there is no instance of the Vue object yet?
export const VueAuth = {
install (Vue, _opts) {
Vue.prototype.$auth = {
test () {
console.log(this.$apollo)
}
}
}
}
(The other plugins are imported and added via. Vue.use() in the main app.js)
Alternatively, I tried...
// ...
install (Vue, { router, store, apollo })
// ...
but as a novice with js, I'm not sure how this works in terms of passing a copy of the passed objects, or if it will mutate the originals/pass by ref. And it's also very explicit and means more overhead if my plugin is to reach out to more plugins further down the line.
Can anyone advise on a clean, manageable way to do this? Do I have to instead alter an instance of Vue instead of the prototype?
In the plugin install function, you do not have access to the Vue instance (this), but you can access other plugins via the prototype. For example:
main.js:
Vue.use(Apollo)
Vue.use(VueAuth) // must be installed after vue-apollo
plugin.js:
export const VueAuth = {
install (Vue) {
Vue.prototype.$auth = {
test () {
console.log(Vue.prototype.$apollo)
}
}
}
}
I found a simple solution for this issue:
In plugin installer you need to add value to not just prototype, but Vue itself to be able to use it globally.
There is a code example:
Installer:
import apiService from "../services/ApiService";
// Service contains 'post' method
export default {
install(Vue) {
Vue.prototype.$api = apiService;
Vue.api = apiService;
}
};
Usage in other plugin:
import Vue from "vue";
...
const response = await Vue.api.post({
url: "/login",
payload: { email, password }
});
Usage in component:
const response = await this.$api.post({
url: "/login",
payload: { email, password }
});
I'm not sure if that's a good solution, but that made my scenario work perfectly.
So, I got around this by converting my property from a plain ol' object into a closure that returns an object, and this seems to have resolved my this scoping issue.
Honestly, I've jumped into Vue with minimal JS-specific knowledge and I don't fully understand how functions and the likes are scoped (and I'm not sure I want to look under that rock just yet......).
export const VueAuth = {
install (Vue, opts) {
Vue.prototype.$auth = function () {
let apollo = this.$apolloProvider.defaultClient
let router = this.$router
return {
logIn: function (email, password) {
apollo.mutate({
mutation: LOGIN_MUTATION,
variables: {
username: email,
password: password,
},
}).then((result) => {
// Result
console.log(result)
localStorage.setItem('token', result.data.login.access_token)
router.go(router.currentRoute.path)
}).catch((error) => {
// Error
console.error('Error!')
console.error(error)
})
},
logOut: function () {
localStorage.removeItem('token')
localStorage.removeItem('refresh-token')
router.go()
console.log('Logged out')
},
}
}
It's a rudimental implementation at the moment, but it'll do for testing.
I am looking how to pass data secretly between two separate components (not parent and child) without using URL params in my Vue2 app. This doesn't mean I am passing secrets but rather I just dont want the user to see it (only for UI considerations).
I know Vue has Props but they are meant for passing data between parent and child component.
In my case, my URL will change but I don't want to pass data via visible params.
Someone claimed to use props without URL params here but I haven't been able to reproduce a working solution (getting undefined each time).
I also checked out these options but they are all using either URL or query params which as we know are visible.
An ugly solution would be to write the data to local storage and then read it there but this creates a lot of overhead and complexity (like what if I only want this data to be read once, etc).
Is there a more elegant solution to this problem?
Thanks!
make props: true for the destination route -- in the index.js file of router
{
path: '/home',
name: 'home',
component: taskChooser,
props: true,
}
define prop in the component e.g props: ['myprop'], - note the quotes
copy the variable you want to pass from the source route into the same name as your prop - in this case myprop
myprop = theVariableThatYouWantToPass
this.$router.replace({name:'home', params:{myprop}});
Make sure that the name of prop and variable are same - the prop is in quotes.
It's working for me.
Thanks #Mayank for pointing me in the correct direction.
Here is the correct syntax that worked for me.
Notice the props in In router index
{
path: '/componentPath',
name: 'componentName',
props: {
header: true,
content: true
},
}
In the component you are redirecting to, define the props as following:
props: {
myProperty: {
type: <DATATYPE>
},
}
Perform redirect as following:
this.$router.push({
name: 'componentName',
params: {
myProperty: <VARIABLE>
}
})
Access props with the this. convention from created or in later lifecycle event.
In this case, the variable name and property name do not have to be the same as it is a simple map. That naming convention would be a curious design choice anyway.
I haven't tested this in Vue 2, but in Vue 3, you can pass a stringified object through the props when you click on a link:
Add props: true to your routes file, for the route.
{
path: 'receipt',
name: 'receipt',
component: () => import('../pages/Receipt.vue'),
props: true,
beforeEnter(to, from, next) {
if (!to.params.receiptData) {
return next({
name: 'dashboard',
params: {
locale: from.params.locale ? from.params.locale : 'en',
},
});
}
return next();
},
},
Include your stringified object as a param for router.push().
const receiptData = {
transferType: 'default',
recipient: receiver.value.name,
referenceNumber: '#B3423424234',
amountSent: formAmount,
transferFee: 0,
};
router.push({
name: 'receipt',
params: {
receiptData: JSON.stringify(receiptData),
},
});
Declare the props as instance data in the component.
<script setup>
import { computed } from 'vue';
const props = defineProps({
receiptData: {
type: String,
required: true,
},
})
console.log('receiptData', props.receiptData);
const parsedReceiptData = computed(() => JSON.parse(props.receiptData));
</script>
I haven't tested an upper limit for size, so be careful about passing a huge object through, and you'll notice I showed a beforeEnter middleware on the route too because, if the user presses F5 to refresh the page, the props will be lost, so in my case, I redirect the user away from the page because the receipt is for one time use only.
I have a list of Tickets which works fine (companytickets), and when clicked.. it opens up a details page (companyticket) for that specific ticket, passing the id to the component.
problem is i can't find out how to access this prop parameter in the created event, since it's not accessable through "this".
companytickets.vue :
viewTicket: function(ticket){
this.$router.push('/companyticket/' + ticket.Id)
// works : this redirects to http://localhost:8180/companyticket/3
}
companyticket.vue
export default {
name: 'CompanyTicket',
props: {
id: {
type: Number,
required: true
}
},
created() {
this.$store.dispatch('getCompanyTicket', this.id)
// ERROR : this.id is undefined...
console.log("Created here :")
}
}
route config
{ path: '/companyticket/:id', component: CompanyTicket, props: true }
Scenario
this.id is "undefined"
when using this.$route.params.id i get the correct id parameter, but in some weird way it claims to use "companytickets/2" (which is the parent page). The Correct should be companyticket/2.
Screenshot of Chrome Dev :
Use object-style or payload for passing params to actions.
Change:
this.$store.dispatch('getCompanyTicket', this.id)
To:
this.$store.dispatch("getCompanyTicket", {
id: this.id
})
Now your files looks like this:
companyticket.vue
created() {
this.$store.dispatch("getCompanyTicket", {
id: this.id
})
}
store.js
actions: {
getCompanyTicket({ commit }, { id }) {
console.log("ID is available now-->", id)
}
}
Vuex
Since you're using Vuex state management pattern, that would be another approach to share data between component.
It allow parent-child communication and same for child-parent (sharing data with props allow only parent-child communication). Inject store into to your root component:
const app = new Vue({
el: '#app',
// provide the store using the "store" option.
// this will inject the store instance to all child components.
store,
})
This is everything you need in your store object:
var store = new Vuex.Store({
state: {
ticketID: Number
},
mutations: {
UPDATE_TICKET_ID(state, ticketId) {
state.ticketId = ticketId;
}
},
actions: {
getCompanyTicket({ commit, state }, { id }) {
commit("UPDATE_TICKET_ID", id)
}
}
}
Also if you want to update state:
The only way to actually change state in a Vuex store is by committing
a mutation
Any property from state will be available in every component:
console.log(this.$store.state.ticketId)
Fiddle : here
I am creating a webapp with Vue 2 with Vuex. I have a store, where I want to fetch state data from a getter, What I want is if getter finds out data is not yet populated, it calls dispatch and fetches the data.
Following is my Vuex store:
const state = {
pets: []
};
const mutations = {
SET_PETS (state, response) {
state.pets = response;
}
};
const actions = {
FETCH_PETS: (state) => {
setTimeout(function() {
state.commit('SET_PETS', ['t7m12qbvb/apple_9', '6pat9znxz/1448127928_kiwi'])
}, 1000)
}
}
const getters = {
pets(state){
if(!state.pets.length){
state.dispatch("FETCH_PETS")
}
return state.pets
}
}
const store = new Vuex.Store({
state,
mutations,
actions,
getters
});
But I am getting following error:
Uncaught TypeError: state.dispatch is not a function(…)
I know I can do this, from beforeMount of Vue component, but I have multiple components which uses same Vuex store, so I have to do it in one of the components, which one should that be and how will it impact other components.
Getters can not call dispatch as they are passed the state not context of the store
Actions can call state, dispatch, commit as they are passed the context.
Getters are used to manage a 'derived state'.
If you instead set up the pets state on the components that require it then you would just call FETCH_PETS from the root of your app and remove the need for the getter
I know this is an older post and I'm not sure if this is good practice, but I did the following to dispatch from a getter in my store module:
import store from "../index"
And used the store inside my getter like this:
store.dispatch("moduleName/actionName")
I did this to make sure data was made available if it was not already present.
*edit:
I want you to be aware of this: Vue form - getters and side effects
This is related to #storsoc note.
If you need to dispatch from your getter you probably are already implementing your state wrong. Maybe a component higher up should already have fetched the data before (state lifting). Also please be aware that getters should only be used when you need to derive other data from the current state before serving it to your template otherwise you could call state directly: this.$store.state.variable to use in methods/computed properties.
Also thing about your lifecycle methods.. you could for example in your mounted or created methods check if state is set and otherwise dispatch from there. If your getter / "direct state" is inside a computed property it should be able to detect changes.
had the same Problem.. also wanted all Vue-Instances to automaticly load something, and wrote a mixin:
store.registerModule('session', {
namespaced: true,
state: {
session: {hasPermission:{}},
sessionLoaded:false
},
mutations: {
changeSession: function (state, value)
{
state.session = value;
},
changeSessionLoaded: function (state)
{
state.sessionLoaded = true;
}
},
actions: {
loadSession(context)
{
// your Ajax-request, that will set context.state.session=something
}
}
});
Vue.mixin({
computed: {
$session: function () { return this.$store.state.session.session; },
},
mounted:function()
{
if(this.$parent==undefined && !this.$store.state.session.sessionLoaded)
{
this.$store.dispatch("session/loadSession");
this.$store.commit("changeSessionLoaded");
}
},
});
because it loads only one per vue-instance and store and it it inlcuded automaticly in every vue-instance, there is no need to define it in every main-app
I use a getter to configure a dynamic page. Essentially, something like this:
getter: {
configuration: function () {
return {
fields: [
{
component: 'PlainText',
props: {},
setPropsFromPageState: function (props, pageState, store) {
// custom logic
}
}
]
};
}
}
Then in the page component, when I am dynamically setting the props on a dynamic component, I can call the setPropsFromPageState(field.props, this.details, this.$store) method for that component, allowing logic to be set at the config level to modify the value of the props being passed in, or to commit/dispatch if needed.
Basically this is just a callback function stored in the getter that is executed in the component context with access to the $store via it.