VueStoreFront: Custom module: state is updated, component not - vue.js

I’m creating a custom module that fetches data in the “afterRegistration” hook and saves the result into the store.
The state is updated (can see the update in VueDevTools), but the component has still the default state. What I’m doing wrong?
// afterRegistration
export function afterRegistration ({Vue, config, store, isServer}) {
store.dispatch(${KEY}/isVisible)
}
// component
export default {
name: 'Test',
components: { Fragment },
props: {
…
},
computed: {
isVisible: el => el.$store.getters['myModule/simpleTest']
}
}

You have lost the reactivity. Set the initial value of simpleTest in state object to whatever you like (from the context i see it's Boolean). There is no getter or setter for this field, if it's not in initial state.

Ok, I found out that I need to dispatch in an AsyncLoader
// afterRegistration
export function afterRegistration ({Vue, config, store, isServer}) {
AsyncDataLoader.push({
execute: ({ route, store, context }) => {
return new Promise ((resolve, reject) => {
store.dispatch(`${KEY}/getInlineTranslationConfig`)
.then(() => resolve(null))
})
}
})
Now it works!

Related

Vuex: getter to dispatch action if no data is in the state

I use vuex for my state as well as fetching data and display it in my application.
But I wonder if I'm doing it right. At the moment I dispatch an fetchDataAsync action from the component mounted hook, and I have an getter to display my data. Below is a code example of how I do it currently.
I wonder if it's necessary. What I really want is a getter, that looks at the state, checks if the data is already there and if the data is not there it is able to dispatch an action to fetch the missing data.
The API of vuex does not allow it so I need to put more logic into my components. E.g. if the data is depended of a prop I need a watcher that looks at the prop and dispatches the fetchDataAsync action.
For me it just feels wrong and I wonder if there is a better way.
let store = new Vuex.Store({
state: {
posts: {}
},
mutations: {
addPost(state, post) {
Vue.set(state.posts, post.id, post);
}
},
actions: {
fetchPostAsync({ commit }, parameter) {
setTimeout(
() =>
commit("addPost", { id: parameter, message: "got loaded asynchronous" }),
1000
);
}
},
getters: {
// is it somehow possible to detect: ob boy, I don't have this id,
// I'd better dispatch an action trying to fetch it...?
getPostById: (state) => (id) => state.posts[id]
}
});
new Vue({
el: "#app",
store,
template : "<div>{{ postToDisplay ? postToDisplay.message : 'loading...' }} </div>",
data() {
return {
parameter: "a"
};
},
computed: {
...Vuex.mapGetters(["getPostById"]),
postToDisplay() {
return this.getPostById(this.parameter);
}
},
methods: {
...Vuex.mapActions(["fetchPostAsync"])
},
mounted() {
this.fetchPostAsync(this.parameter);
}
});
I also created a codepen
Personally I think the solution you suggested (adding a watcher that dispatches fetchPostAsync if the post is not found) is the best one. As another commenter stated, getters should not have side effects.

How to test VueX state change in the component using vuex-test-utils?

I have a very simple component that relies on the data from the backend being loaded in the store, and I wan to write a unit test for this flow.
Basically, my template code is:
<div class="my-component">
<div class="loading-screen" v-if="loading"></div>
<div class="content" v-if="!loading"></div>
</div
Loading is a computed value that comes from the store.
I want to test it with the following test scenario:
describe('My Component', () => {
let wrapper;
let actions;
let store;
let state;
let mutations;
beforeEach(() => {
actions = {};
state = {
loading: true,
};
mutations = {
finishLoading: (state) => { state.loading = false },
};
store = new Vuex.Store({
modules: {
myModule: {
namespaced: true,
state,
actions,
mutations,
}
}
});
});
test('Calls store action for data and then shows the page', () => {
wrapper = mount(MyComponent, { store, localVue });
expect(wrapper.find('.loading-screen').isVisible()).toEqual(true);
expect(wrapper.find('.content').exists()).toEqual(false);
store.commit('finishLoading');
expect(wrapper.find('.loading-screen').exists()).toEqual(false);
expect(wrapper.find('.content').isVisible()).toEqual(true);
});
});
The part after store.commit('finishLoading') fails. How can I trigger the component to update based on the store data?
Try to add this line after store.commit('finishLoading').
await wrapper.vm.$nextTick();
And remember to make your function async.
test('Calls store action for data and then shows the page', async () => {
I found out later that I also missed one thing!
My store is namespaced, so naturally as I don't create a NamespacedHelper for it, I need to call the following mutation:
store.commit('applicationApply/finishLoading');
This is an addition to the valid fix above, which resolves the main question.

How do correctly add a getter to a Vuex Module?

I'm trying to create a Vuex module whenever I register the module, I get a state is undefined, even though there is nothing calling the getter I had just made. I'm able to call actions correctly with no errors.
This is my module. customer.js
export default {
namespaced: true,
state: {
login: false,
},
getters: {
isLoggedIn: (state) => {
console.log(state);
state.login;
}
},
mutations: {
set_login: (state, login) => {
state.login = login;
},
set_orders: (state, orders) => {
state.orders = orders;
},
},
actions: {
newsletter_subscribe: (context, email) => {
//- TODO
},
}
}
I have register via the registerModule function.
import Customer from './customer';
store.registerModule('customer', Customer, {
preserveState: true
});
Whenever I have developer tools open it just alerts me that.
Uncaught ReferenceError: state is not defined
at isLoggedIn (customer.js:11)
at wrappedGetter (vuex.esm.js:734)
Am I doing anything wrong with my getter?
I've only noticed the getter being called with Vue Dev tools open as I tried putting an alert in the getter to see what else could be triggering without the state being passed in.
I managed to solve it by giving my store a blank state!
const store = new Vuex.Store({
state: {}
});
And registering modules as normal.

vuejs2: how can i destroy a watcher?

How can i destroy this watcher? I need it only one time in my child component, when my async data has loaded from the parent component.
export default {
...
watch: {
data: function(){
this.sortBy();
},
},
...
}
gregor ;)
If you construct a watcher dynamically by calling vm.$watch function, it returns a function that may be called at a later point in time to disable (remove) that particular watcher.
Don't put the watcher statically in the component, as in your code, but do something like:
created() {
var unwatch = this.$watch(....)
// now the watcher is watching and you can disable it
// by calling unwatch() somewhere else;
// you can store the unwatch function to a variable in the data
// or whatever suits you best
}
More thorough explanation may be found from here: https://codingexplained.com/coding/front-end/vue-js/adding-removing-watchers-dynamically
Here is an example:
<script>
export default {
data() {
return {
employee: {
teams: []
},
employeeTeamsWatcher: null,
};
},
created() {
this.employeeTeamsWatcher = this.$watch('employee.teams', (newVal, oldVal) => {
this.setActiveTeamTabName();
});
},
methods: {
setActiveTeamTabName() {
if (this.employee.teams.length) {
// once you got your desired condition satisfied then unwatch by calling:
this.employeeTeamsWatcher();
}
},
},
};
</script>
If you are using vue2 using the composition-api plugin or vue3, you can use WatchStopHandle which is returned by watch e.g.:
const x = ref(0);
setInterval(() => {
x.value++;
}, 1000);
const unwatch = watch(
() => x.value,
() => {
console.log(x.value);
x.value++;
// stop watch:
if (x.value > 3) unwatch();
}
);
For this kind of stuff, you can investigate the type declaration of the API, which is very helpful, just hover the mouse on it, and it will show you a hint about what you can do:

mapState with setter

I would like to assign setter methods via mapState. I currently use a workaround where I name the variable that I am interested in (todo) as a temporary name (storetodo) and then refer to it in another computed variable todo.
methods: {
...mapMutations([
'clearTodo',
'updateTodo'
])
},
computed: {
...mapState({
storetodo: state => state.todos.todo
}),
todo: {
get () { return this.storetodo},
set (value) { this.updateTodo(value) }
}
}
I would like to skip the extra step and define the getter, setter directly within mapState.
Why would I want to do this?
The normal approach would be use mapMutations/mapActions & mapState/mapGetters
without the computed get/set combination that I have illustrated above and to reference the mutation directly in the HTML:
<input v-model='todo' v-on:keyup.stop='updateTodo($event.target.value)' />
The getter/setter version allows me to simply write:
<input v-model='todo' />
You can't use a getter/setter format in the mapState
what you can try is directly return the state in your get() and remove mapState from the computed property
computed: {
todo: {
get () { return this.$store.state.todos.todo},
set (value) { this.updateTodo(value) }
}
}
Here is a related but not same JsFiddle example
This is my current workaround. Copied from my personal working project
// in some utils/vuex.js file
export const mapSetter = (state, setters = {}) => (
Object.keys(state).reduce((acc, stateName) => {
acc[stateName] = {
get: state[stateName],
};
// check if setter exists
if (setters[stateName]) {
acc[stateName].set = setters[stateName];
}
return acc;
}, {})
);
In your component.vue file
import { mapSetter } from 'path/to/utils/vuex.js';
export default {
name: 'ComponentName',
computed: {
...mapSetter(
mapState({
result: ({ ITEMS }) => ITEMS.result,
total: ({ ITEMS }) => ITEMS.total,
current: ({ ITEMS }) => ITEMS.page,
limit: ({ ITEMS }) => ITEMS.limit,
}),
{
limit(payload) {
this.$store.dispatch({ type: TYPES.SET_LIMIT, payload });
},
},
)
},
}
now you can use the v-model bindings. l
Another way of approaching that is using store mutations like below:
//in your component js file:
this.$store.commit('setStoretodo', storetodo)
Assuming you define setStoretodo in mutations of your vuex store instance (which is something recommended to have anyways):
//in your vuex store js file:
state:{...},
actions: {...}
...
mutations: {
setStoretodo(state, val){
state.storetodo = val
},
...
}
...
That keeps the property reactive as mapState will grab the updated value and it will be rendered automatically.
Surely, that's not as cool as just writing this.storetodo = newValue, but maybe someone will find that helpful as well.