In vue 2+ I can easily get the instance of this as a result I can write something like this,
// main.js
app.use(ElMessage)
// home.vue
this.$message({
showClose: true,
message: 'Success Message',
type: 'success',
})
What should I do for vue 3 as,
Inside setup(), this won't be a reference to the current active
instance Since setup() is called before other component options are
resolved, this inside setup() will behave quite differently from this
in other options. This might cause confusions when using setup() along
other Options API. - vue 3 doc.
Using ElMessage directly
ElementPlus supports using ElMessage the same way as $message(), as seen in this example:
import { ElMessage } from 'element-plus'
export default {
setup() {
const open1 = () => {
ElMessage('this is a message.')
}
const open2 = () => {
ElMessage({
message: 'Congrats, this is a success message.',
type: 'success',
})
}
return {
open1,
open2,
}
}
}
Using $message()
Vue 3 provides getCurrentInstance() (an internal API) inside the setup() hook. That instance allows access to global properties (installed from plugins) via appContext.config.globalProperties:
import { getCurrentInstance } from "vue";
export default {
setup() {
const globals = getCurrentInstance().appContext.config.globalProperties;
return {
sayHi() {
globals.$message({ message: "hello world" });
},
};
},
};
demo
Note: Being an internal API, getCurrentInstance() could potentially be removed/renamed in a future release. Use with caution.
Providing a different method where the idea is to set a globally scoped variable to the _component property of the viewmodel/app or component:
pageVM = Vue.createApp({
data: function () {
return {
renderComponent: true,
envInfo: [],
dependencies: [],
userGroups: []
}
},
mounted: function () {
//Vue version 3 made it harder to access the viewmodel's properties.
pageVM_props = pageVM._component;
this.init();
},
Related
Background
I recently upgraded from Vue v2.6.14 to Vue 2.7 by following this guide: https://blog.vuejs.org/posts/vue-2-7-naruto.html.
I made some changes (e.g., removing #vue/composition-api and vue-template-compiler, upgrading to vuex-composition-helpers#next, etc.).
Problem
The application loads for the most part, but now I get a ton of console errors:
[Vue warn]: Vue 2 does not support readonly arrays.
It looks like even just console.log(workspaces.value); (see code below) raises the warning.
Question
How do I resolve this issue?
Thank you!
Code
<script lang="ts">
import {
defineComponent,
onMounted,
computed,
} from 'vue';
import { createNamespacedHelpers } from 'vuex-composition-helpers';
import {
modules,
actionTypes,
getterTypes,
} from '#/store/types';
import _ from 'lodash';
const workspaceModule = createNamespacedHelpers(modules.WORKSPACE_MODULE);
export default defineComponent({
setup() {
const { newWorkspace, listWorkspaces } = workspaceModule.useActions([
actionTypes.WorkspaceModule.NEW_WORKSPACE,
actionTypes.WorkspaceModule.LIST_WORKSPACES,
]);
const { workspaces } = workspaceModule.useGetters([
getterTypes.WorkspaceModule.GET_WORKSPACES,
]);
onMounted(async () => {
await listWorkspaces({
Archived: false,
Removed: false,
});
console.log(workspaces.value);
});
return {
/*
workspacesSorted: computed(() => {
return _.orderBy(workspaces.value, ['LastUpdated'], ['desc']);
}),
*/
}
}
});
</script>
src/store/modules/workspace/getters.ts
import { GetterTree } from 'vuex';
import { WorkspaceState } from './types';
import { RootState } from '../../types';
import { getterTypes } from '../../types';
export const getters: GetterTree<WorkspaceState, RootState> = {
[getterTypes.WorkspaceModule.GET_WORKSPACES](context: WorkspaceState) {
return context.Workspaces;
},
[getterTypes.WorkspaceModule.GET_ALL_WORKSPACES](context: WorkspaceState) {
return context.AllWorkspaces;
}
}
src/store/modules/workspace/actions.ts
export const actions: ActionTree<WorkspaceState, RootState> = {
async [actionTypes.WorkspaceModule.LIST_WORKSPACES]({ commit }, payload: ListWorkspace) {
const wss = await list(payload.Archived, payload.Removed);
wss.forEach((ws) => {
ws.Archived = payload.Archived;
ws.Removed = payload.Removed;
});
commit(mutationTypes.WorkspaceModule.SET_WORKSPACES, wss);
},
};
src/store/modules/workspace/actions.ts
export const mutations: MutationTree<WorkspaceState> = {
[mutationTypes.WorkspaceModule.SET_WORKSPACES](ctx: WorkspaceState, wss: Workspace[]) {
ctx.Workspaces = wss;
},
};
src/service/useWorkspace.ts
const list = async(archived: boolean, removed: boolean) => {
const res = await get<Workspace[], AxiosResponse<Workspace[]>>('/workspace/list', {
params: {
archived,
removed,
}
});
return success(res);
};
When I call store.state.WorkspaceModule.Workspaces directly (either in the console or in computed), I get no errors:
import { useStore } from '#/store';
export default defineComponent({
setup() {
const store = useStore();
onMounted(async () => {
await listWorkspaces({
Archived: false,
Removed: false,
});
console.log(store.state.WorkspaceModule.Workspaces);
});
return {
workspacesSorted: computed(() =>
store.state.WorkspaceModule.Workspaces
),
}
}
});
This might be because workspaces is based on a getter, which are read-only. As mentioned in the blog you were referring to, readonly is not supported for arrays in Vue 2.7:
readonly() does create a separate object, but it won't track newly added properties and does not work on arrays.
It was (partially) supported for arrays in the Vue 2.6 Composition Api Plugin though:
readonly() provides only type-level readonly check.
So that might be causing the error. If it is mandatory for you, you might need to upgrade to vue3, or stick with 2.6 for a while. The composition Api plugin is maintained until the end of this year...
A workaround may be to skip the getter and access the state directly, since it is a quite simple getter which only returns the current state of Workspaces.
Hope this helps.
I have a Vue 3 app and an express server. The server does not serve any pages just acts as an API so no socket.io/socket.io.js file is sent to client.
I am trying to set up socket.io in one of my vue components but whatever I try does not work. Using vue-3-socket.io keeps giving 't.prototype is undefined' errors.
I have tried vue-socket.io-extended as well with no luck.
Any advice would be appreciated as to the reason and solution for the error above, I have tried various SO solutions without success, and the best way forward.
You can use socket.io-client. I have used socket.io-client of 4.4.1 version.
step: 1
Write class inside src/services/SocketioService.js which returns an instance of socketio.
import {io} from 'socket.io-client';
class SocketioService {
socket;
constructor() { }
setupSocketConnection() {
this.socket = io(URL, {
transports: ["websocket"]
})
return this.socket;
}
}
export default new SocketioService();
Step 2:
Import SocketioService in App.vue. You can instantiate in any lifecycle hook of vue. I have instantiated on mounted as below. After instantiation, I am listening to welcome and notifications events and used quasar notify.
<script>
import { ref } from "vue";
import SocketioService from "./services/socketio.service.js";
export default {
name: "LayoutDefault",
data() {
return {
socket: null,
};
},
components: {},
mounted() {
const socket = SocketioService.setupSocketConnection();
socket.on("welcome", (data) => {
const res = JSON.parse(data);
if (res?.data == "Connected") {
this.$q.notify({
type: "positive",
message: `Welcome`,
classes: "glossy",
});
}
});
socket.on("notifications", (data) => {
const res = JSON.parse(data);
let type = res?.variant == "error" ? "negative" : "positive";
this.$q.notify({
type: type,
message: res?.message,
position: "bottom-right",
});
});
},
};
</script>
I created a Vue component which exports an async function. This component acts as a wrapper for calling my API. It's based on axios with a caching component that relies on localforage for some short lived persistence.
import localforage from 'localforage'
import memoryDriver from 'localforage-memoryStorageDriver'
import { setup } from 'axios-cache-adapter'
export default {
async cache() {
// Register the custom `memoryDriver` to `localforage`
await localforage.defineDriver(memoryDriver)
// Create `localforage` instance
const store = localforage.createInstance({
// List of drivers used
driver: [
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
memoryDriver._driver
],
// Prefix all storage keys to prevent conflicts
name: 'tgi-cache'
})
// Create `axios` instance with pre-configured `axios-cache-adapter` using a `localforage` store
return setup({
// `axios` options
baseURL: 'https://my.api',
cache: {
maxAge: 2 * 60 * 1000, // set cache time to 2 minutes
exclude: { query: false }, // cache requests with query parameters
store // pass `localforage` store to `axios-cache-adapter`
}
})
}
}
Here is how I am importing and using this component in my views:
import api from '#/components/Api.vue'
export default {
data() {
return {
userId: this.$route.params.id,
userData: ''
}
},
methods: {
loadClient(userId) {
const thisIns = this;
api.cache().then(async (api) => {
const response = await api.get('/client/find?id='+userId)
thisIns.userData = response.data.data[0]
}).catch(function (error) {
console.log(error)
})
},
},
created() {
this.loadClient(this.userId)
},
}
I can import this component and everything appears to work. I get data back from my API. However, immediately after every call, I get an error:
TypeError: Cannot read property 'cache' of undefined
Which references this line:
api.cache().then(async (api) => {
I am unable to understand why this is happening, or what it means. The error itself indicates that the component I am importing is undefined, though that's clearly not the case; if it were, the API call would ultimately fail I would suspect. Instead, I am lead to believe that perhaps I am not constructing/exporting my async cache() function properly.
Upon further review, I don't actually understand why the author has implemented it the way he has. Why would you want to create an instance of localForage every single time you make an API call?
I've opted not to use a component and to only instantiate an instance of localForage once.
main.js
import localforage from 'localforage'
import memoryDriver from 'localforage-memoryStorageDriver'
import { setup } from 'axios-cache-adapter'
// Register the custom `memoryDriver` to `localforage`
localforage.defineDriver(memoryDriver)
// Create `localforage` instance
const localforageStore = localforage.createInstance({
// List of drivers used
driver: [
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
memoryDriver._driver
],
// Prefix all storage keys to prevent conflicts
name: 'my-cache'
})
Vue.prototype.$http = setup({
baseURL: 'https://my.api',
cache: {
maxAge: 2 * 60 * 1000, // set cache time to 2 minutes
exclude: { query: false }, // cache requests with query parameters
localforageStore // pass `localforage` store to `axios-cache-adapter`
}
})
the view
export default {
data() {
return {
userId: this.$route.params.id,
userData: ''
}
},
methods: {
loadClient(userId) {
const thisIns = this;
thisIns.$http.get('/client/find?id='+userId)
.then(async (response) => {
thisIns.userData = response.data.data[0]
})
.catch(function (error) {
console.log(error)
})
},
},
created() {
this.loadClient(this.userId)
},
}
We are building a web application using Vue JS and PHP, we are new to Vue JS. The server-side execution is fine, the API is able to fetch data as JSON. While trying out a static array display before making the API call, we find that the function in imported "app.js" is not getting called and the table displayed is empty. Please let us know what we might be doing wrong. Appreciate your help.
import Vue from 'vue';
export const MY_CONST = 'Vue.js';
export let memberList = new Vue({
el: '#members',
data: {
members: []
},
mounted: function () {
this.getAllMembers();
},
methods: {
getAllMembers: function () {
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
memberList.members = response.data.members;
});
*/
memberList.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}
}
});
This is the Vue component. The members object is empty.
<script>
import * as mykey from './app.js'
export default {
name: 'Home',
props: {
msg: String
},
data() {
return {
message: `Hello ${mykey.MY_CONST}!`,
members: mykey.memberList.members
}
}
};
</script>
You can also use this reference for current instance reference:
getAllMembers: function () {
var me = this;
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
// direct this not works here but we have
//saved this in another variable and scope of a var is there
me.members = response.data.members;
});
*/
// this reference works fine here.
this.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}
In almost all guides, tutorial, posts, etc that I have seen on vuex module registration, if the module is registered by the component the createNamespacedHelpers are imported and defined prior to the export default component statement, e.g.:
import {createNamespacedHelpers} from 'vuex'
const {mapState} = createNamespacedHelpers('mymod')
import module from '#/store/modules/mymod'
export default {
beforeCreated() {
this.$store.registerModule('mymod', module)
}
}
this works as expected, but what if we want the module to have a unique or user defined namespace?
import {createNamespacedHelpers} from 'vuex'
import module from '#/store/modules/mymod'
export default {
props: { namespace: 'mymod' },
beforeCreated() {
const ns = this.$options.propData.namespace
this.$store.registerModule(ns, module)
const {mapState} = createNamespacedHelpers(ns)
this.$options.computed = {
...mapState(['testVar'])
}
}
}
I thought this would work, but it doesnt.
Why is something like this needed?
because
export default {
...
computed: {
...mapState(this.namespace, ['testVar']),
...
},
...
}
doesnt work
This style of work around by utilising beforeCreate to access the variables you want should work, I did this from the props passed into your component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above
}
}
Hope you find this useful.
All posted answers are just workarounds leading to a code that feels verbose and way away from standard code people are used to when dealing with stores.
So I just wanted to let everyone know that brophdawg11 (one of the commenters on the issue #863) created (and open sourced) set of mapInstanceXXX helpers aiming to solve this issue.
There is also series of 3 blog posts explaining reasons behind. Good read...
I found this from veux github issue, it seems to meet your needs
https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
{
props: ['namespace'],
computed: mapState({
state (state) {
return state[this.namespace]
},
someGetter (state, getters) {
return getters[this.namespace + '/someGetter']
}
}),
methods: {
...mapActions({
someAction (dispatch, payload) {
return dispatch(this.namespace + '/someAction', payload)
}
}),
...mapMutations({
someMutation (commit, payload) {
return commit(this.namespace + '/someMutation', payload)
})
})
}
}
... or maybe we don't need mapXXX helpers,
mentioned by this comment https://github.com/vuejs/vuex/issues/863#issuecomment-439039257
computed: {
state () {
return this.$store.state[this.namespace]
},
someGetter () {
return this.$store.getters[this.namespace + '/someGetter']
}
},