I try to migrate Vue 2 plugin to Vue 3, and in install function i have construction like this:
install(Vue, options) {
const reactiveObj = {
// ...
};
Vue.prototype.$obj = Vue.observable(reactiveObj);
}
And then I can access it with this.$obj in any component and it is reactive when reactiveObj changes. But in Vue 3 I try to make something like this:
import {reactive} from 'vue';
install(app, options) {
const reactiveObj = reactive({
// ...
});
app.config.globalProperties.$obj = reactiveObj;
}
And then I can access it with this.$obj, it is Proxy object, but it is not reactive when reactiveObj changes. How can I make it reactive?
I post the codes that works for me when I changing the value of $obj in my component. Maybe it could help you to understand the problems in your code. here is the "myPlugin.js" file where I defined the plugin:
myPlugin.js:
import {reactive} from 'vue';
export default {
install(app, options) {
const reactiveObj = reactive({
id: 1,
name: "my-name"
});
app.config.globalProperties.$obj = () => {
return reactiveObj
};
}
}
And here is the registration of plugin in my main.js file:
import { createApp } from 'vue'
import App from './App.vue'
import myPlugin from "./plugins/myPlugin"
const app = createApp(App)
app.use(myPlugin);
app.mount('#app')
And here is the code of my component where by clicking the button the values of $obj is changed and you can see that it is reactive:
component.vue:
<template>
<div>
<p>{{ $obj() }}</p>
<button #click="myFunc">click to change</button>
</div>
</template>
<script>
import {getCurrentInstance, reactive} from "vue";
export default {
setup() {
const thisApp= getCurrentInstance()
const reactiveObj1 = reactive({
id: 2,
name: "new-name"
});
const myFunc = function () {
thisApp.appContext.config.globalProperties.$obj().name = reactiveObj1.name
thisApp.appContext.config.globalProperties.$obj().id = reactiveObj1.id
}
return {
reactiveObj1,
myFunc
}
}
}
</script>
I used getCurrentInstance in my component, because I am using composition API style. Maybe you do not need that if you are using "options API" style.
Related
I am trying to implement a code editor with remote users and currently I have a basic code Editor component like this.
...
<script setup>
import ace, { Range } from 'ace-builds'
import "ace-builds/webpack-resolver";
import 'ace-builds/src-noconflict/snippets/javascript'
import { reactive, ref, onMounted } from 'vue'
const ace_text_area = ref(null)
const props = defineProps({ value: String })
const state = reactive({
aceEditor:null,
value:""
})
onMounted(()=>{
state.value = props.value
state.aceEditor = ace.edit(ace_text_area.value, {value: state.value});
})
</script>
How do I add a remote user using addDynamicMarker ?
Follow the official example to export your own useStore, and then use it in the component.
import { createStore, Store, useStore as baseUseStore } from 'vuex';
export const key: InjectionKey<Store<RootState>> = Symbol();
export function useStore() {
return baseUseStore(key);
}
use in the component
setup() {
const store = useStore();
const onClick = () => {
console.log(store)
store.dispatch('user/getUserInfo');
}
return {
onClick,
}
},
After running, store is undefined.
It can be obtained normally when I use it in the methods attribute
methods: {
login() {
this.$store.dispatch('user/getToken')
}
}
why? how to fix it
In that simplifying useStore usage tutorial, you still need to register the store and key in main.ts as they did. You will get undefined if you don't do this:
// main.ts
import { store, key } from './store'
const app = createApp({ ... })
// pass the injection key
app.use(store, key)
The reason is that baseUseStore(key) has no meaning until that's done.
Anybody knows how to use mapState or mapGetters with Vue 3 in the setup function ?
I know that is possible to use the store with the useStore hook but with this hook We import all the store while with mapState or mapGetters we can specify a module :
// ...
computed: {
...mapGetters('myModule', [
'myStateVariable'
]
)
//...
Perhaps something like this:
import { computed } from 'vue';
import { useStore } from 'vuex';
const module = 'myModule';
export default {
setup() {
const store = useStore();
return {
// getter
one: computed(() => store.getters[`${module}/myStateVariable`],
// state
two: computed(() => store.state[module].myStateVariable,
};
},
};
As far as I can tell, they get flattened so you can't use myModule/myStateVariable, but myStateVariable should work.
This could be something that may change as Vuex gets to RC releases, but for now if you try to have the same getter twice, you get this error
With vue 3 and vuex 4 I managed to do it like this:
suppose we have a store shown below:
our general store index.js (the one on the bottom) would be like this:
import { createStore, createLogger } from 'vuex';
import module1 from '#/store/module1';
import module2 from '#/store/module2';
export default createStore({
modules: {
module1: module1,
module2: module2,
},
plugins: process.env.NODE_ENV !== 'production'
? [createLogger()]
: []
})
whereas the modules would each have such an index.js:
import * as getters from './getters'
import * as actions from './actions'
import mutations from './mutations'
const state = {
postId: 10111,
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}
and the getter in each one of the module would be like this:
export const getPostId = state => {
return state.postId
}
finally in a component you could access the getters like that:
<template>
<div>
<div class="major_container">
<p>{{ postIdFromModule1 }}</p>
<p>{{ postIdFromModule2 }}</p>
</div>
</div>
</template>
<script>
import { computed } from "vue";
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const postIdFromModule1 = computed(() => store.getters["module1/getPostId"]);
const postIdFromModule2 = computed(() => store.getters["module2/getPostId"]);
return {
postIdFromModule1,
postIdFromModule2,
};
},
};
</script>
Great, now you can use the modules namespaced!
The best way to use mapActions in a vue3 style SFC is to use mapActions in the setup() function's return
import { mapActions } from "vuex"
setup() {
return {
...mapActions("myModule", ["doSomething"])
}
}
In spite of my understanding that NUXT does namespacing automatically. Because of this, I am unable to test or reference the store in any of my testing modules. Can anyone give me a tip? Maybe where I can edit the namespacing property in a Nuxt app?
Here is the code below for the component, store, and the test.
ButtonComponent.vue:
<template>
<v-container>
<v-btn #buttonClick v-model="value"></v-btn>
</v-container>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data: {
return {
value: 25
}
}
methods: {
buttonClick(event) {
this.$store.dispatch('buttonComponent/setNewValue', valuePassedIn)
},
},
}
</script>
<style scoped></style>
buttonComponent.spec.js:
import Component from '../../Component'
import { mount, createLocalVue } from '#vue/test-utils'
import expect from 'expect'
import Vue from 'vue'
import Vuex from 'vuex'
import Vuetify from 'vuetify'
const localVue = createLocalVue()
localVue.use(Vuex)
Vue.use(Vuetify)
describe('Component', () => {
let store
let vuetify
let actions
beforeEach(() => {
actions = {
actionClick: jest.fn()
}
store = new Vuex.Store({
actions,
})
vuetify = new Vuetify()
})
it('method sends value to store when button is clicked', async () => {
const wrapper = mount(Component, {
store,
localVue,
vuetify,
})
wrapper.find('.v-btn').trigger('click')
expect(actions.actionClick).toHaveBeenCalledWith('buttonComponent/setNewValue', 25)
})
})
buttonComponent.js:
export const state = () => ({
value: 0,
})
export const mutations = {
SET_TO_NEW_VALUE(state, value) {
state.value = value
},
}
export const actions = {
setNewValue({ commit }, value) {
commit('SET_TO_NEW_VALUE', value)
},
}
Just so that I don't have to write it again here, I'll link you to an article I just posted that walks through the setup process to so you can test your Nuxt stores with Jest: https://medium.com/#brandonaaskov/how-to-test-nuxt-stores-with-jest-9a5d55d54b28
I'm going crazy, I have a working api that sends data, I connected it to a VueJS app and it was working fine. I'm trying to implement Vuex and I'm stuck. Here's my store.js file
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios'
Vue.use(Vuex);
const state = {
message: "I am groot",
articles: []
}
const getters = {
getArticles: (state) => {
return state.articles;
}
}
const actions = {
getArticles: ({ commit }, data) => {
axios.get('/articles').then( (articles) => {
commit('GET_ARTICLES', articles);
console.log(articles); // Trying to debug
}, (err) => {
console.log(err);
})
}
}
const mutations = {
GET_ARTICLES: (state, {list}) => {
state.articles = list;
}
}
const store = new Vuex.Store({
state,
getters,
mutations,
actions,
mutations
});
console.log(store.state.articles); // this lines works but data is empty
export default store
The console.log within axios call doesn't run and store.state.articles is empty. I must be missing something. I'm just trying to console the articles data on page load...
Please help, I'm near insanity :)
Component :
<template>
<div class="container">
<h1>Test component yo !</h1>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
name: 'Test',
computed: {
message() {
return this.$store.state.message
}
},
mounted: () => {
this.$store.dispatch('getArticles')
}
}
</script>
App.js :
import Vue from 'vue';
import ArticlesViewer from './articles_viewer.vue';
import UserArticles from './user_articles.vue';
import App from './app.vue'
import store from './store'
new Vue({
el: '#app-container',
store,
render: h => h(App)
})
You define the mounted lifecycle hook of your component using an arrow function.
As per the documentation:
Don’t use arrow functions on an instance property or callback (e.g. vm.$watch('a', newVal => this.myMethod())). As arrow functions are bound to the parent context, this will not be the Vue instance as you’d expect and this.myMethod will be undefined.
You should define it like so:
mounted: function () {
this.$store.dispatch('getArticles');
}
Or, use the ECMAScript 5 shorthand:
mounted() {
this.$store.dispatch('getArticles');
}
Now, your dispatch method will be called correctly, populating your articles array.