I use a vuex store to handle a session and loading default sessions for different pages onCreated hook. Data is changing and most of the time it works fine but now I noticed even though I change the data, the setter / mutation is not called.
session.js (mixin to load default session from main component)
export default {
data () {
return {
defaultSession: {}
}
},
created () {
this.$store.dispatch('session/update', { data: this.defaultSession })
},
computed: {
sessionData: {
get () {
console.log('GETTER')
return this.$store.getters['session/sessionData']
},
set (session) {
console.log('SETTER')
this.$store.dispatch('session/update', { data: session })
}
}
}
}
store
export default {
namespaced: true,
state: {
session: {},
},
getters: {
session (state) {
return state.session
},
sessionData (state) {
return state.session.data
},
},
mutations: {
UPDATE (state, session) {
state.session = session
console.log('[Session] Updated')
}
},
actions: {
update: ({ commit }, data) => {
commit('UPDATE', data)
}
}
defaultSession example (in component)
defaultSession: {
name: '',
viewId: '',
defaultImage: null,
dataTable: [{
title: '',
data: [],
selected: [],
}],
imageTableData: [],
selectedIcons: []
},
So in my component I access <input v-model="sessionData.name"/> for instance, but neither the setter, nor the mutation is called. (The object is quite nested, because I also store table data inside.)
UPDATE (possible workaround):
watch: {
sessionData: {
deep: true,
handler() {
//commit changes here
}
}
}
Related
What we need to do: We need to feature flag a few things in our current state machine
My ideal solution: Always load, no matter what state is, all feature flags and assign them to the state machine context
Attempts: Tried using async actions and invoke services, however, I cannot find a way to always run either of them
This basically my state machine and how I envisioned loading feature flag. However, the invoke.src function just gets called for the first time when I'm first loading the state machine.
Every time that I hydrate the state machine and the state machine is in one of the state, for example create, the invoke.src function does not get called therefore no FF is loaded into the context
const stateMachine = createStateMachine({
id: 'state-machine',
invoke: {
src: async () => {
return await featureFlagService.load();
},
onDone: {
actions: assign(_context, event) => ({ featureFlagEvaluations: event.data }),
}
},
states: {
'create': { ... },
'retrieve': { ... },
}
});
Does anyone have any idea of how to implement such use case?
You should use the actor model approach. Each time when you need to refresh/fetch the FF you should spawn FF-machine and on done call parentSend() message which will update the context to your main SM(state-machine)
const stateMachine = createStateMachine({
id: 'state-machine',
invoke: {
src: async () => {
return await featureFlagService.load();
},
onDone: [{
actions: assign({
ffActorRef: () => spawn(featureFlagMachine, 'ffActor'),
}),
}],
states: {
'create': { ... },
'retrieve': { ... },
},
on:{
REFRESH_FEATURE_FLAG : [{
actions: assign(_context, event) => ({ featureFlagEvaluations: event.data }),
}]
}
});
const featureFlagMachine = createStateMachine({
id: 'ff-machine',
initial: 'retrieve',
invoke: {
src: async () => {
return await featureFlagService.load();
},
onDone: [{
actions: ['notifyRefresh']
}],
states: {
'create': { ... },
'retrieve': { ... },
},
},
{
actions: {
notifyRefresh: sendParent((ctx, event) => {
return {
type: 'REFRESH_FEATURE_FLAG',
data: { featureFlagEvaluations: event.data },
};
}),
},
}
}
);
First I defined Types, Severities, and Statuses as [] and returned them in data().
Then I filled them with data in the methods getTypes(), getSeverities(), and getStatuses().
I want to use Types, Severities, and Statuses in the method getName()(just has console.log() as an example for now).
I noticed when debugging getNames(), type in the second for loop is undefined. Is it because the method is using Type before it was assigned values in getTypes()? How can I make it work?
Note: Types, Severities, and Statuses do get assigned values in the methods getTypes(), getSeverities(), and getStatuses(), the issues is how to use the data in other methods.
<script>
import IssuesTable from '../MyIssuesPage/IssuesTable.vue'
import AddIssue from '../MyIssuesPage/AddIssue.vue'
import axios from 'axios'
export default {
props: ['id', 'project', 'issuesList', 'index'],
components: { IssuesTable, AddIssue },
data() {
return {
Issues: this.issuesList[this.index],
tab: null,
items: [{ tab: 'Issues' }, { tab: 'Calender' }, { tab: 'About' }],
Types: [],
Severities: [],
Statuses: [],
}
},
setup() {
return {
headers: [
{ text: 'Title', value: 'title' },
{ text: 'Description', value: 'description' },
{ text: 'Estimate', value: 'time_estimate' },
{ text: 'Assignees', value: 'userid' },
{ text: 'Type', value: 'issueTypeId' },
{ text: 'Status', value: 'issueStatusId' },
{ text: 'Severity', value: 'issueSeverityId' },
],
}
},
mounted() {
this.getTypes(), this.getSeverities(), this.getStatuses(), this.getNames()
},
methods: {
getTypes() {
axios
.get('http://fadiserver.herokuapp.com/api/v1/my-types')
.then(response => {
this.Types = response.data
})
.catch(error => {
console.log(error)
})
},
getSeverities() {
axios
.get('http://fadiserver.herokuapp.com/api/v1/my-severities')
.then(response => {
this.Severities = response.data
})
.catch(error => {
console.log(error)
})
},
getStatuses() {
axios
.get('http://fadiserver.herokuapp.com/api/v1/my-status')
.then(response => {
this.Statuses = response.data
})
.catch(error => {
console.log(error)
})
},
getNames() {
for (var issue of this.Issues) {
for (var type of this.Types) {
if (issue.issueTypeId == type.id) console.log('test')
}
}
},
},
}
</script>
First of all, use created() instead of mounted() for calling methods that fetch data.
Next, you need to call getNames() only after all fetch methods complete.
created() {
this.getTypes()
.then(this.getSeverities())
.then(this.getStatuses())
.then(this.getNames());
}
In order to chain methods like this you need to put return statement before each axios like this
getTypes() {
return axios
.get("https://fadiserver.herokuapp.com/api/v1/my-types")
.then((response) => {
this.Types = response.data;
})
.catch((error) => {
console.log(error);
});
}
In this component, I see you are receiving issuesList and index props from the outside. I cannot know those values but you can console.log both of them inside created() and see what is happening because issuesList[index] is undefined.
That probably means issuesList is an array and that index does not exist in that array.
I am developing my first application in vuejs.
It's a form with several steps that share a header and a footer and as I go along I send to the store.js the info I'm putting in each section.
I have been stuck for a couple of days in the fact that I am not able to retrieve the info from the first sections of the form to be able to show a summary of the info entered in the last step.
In each step every time I click on the advance button I send the info to store.js and I navigate to the next component.
This would be an example of the action in one of the components
onSubmit() {
const formData = {
selectedService: this.focusService,
selectedItem: this.selectedItem,
selectedShop: this.selectedShop,
selectedItemId: this.selectedItemId
};
this.$store.dispatch('formInfo', {
selectedService: formData.selectedService,
selectedItem: formData.selectedItem,
selectedShop: formData.selectedShop,
selectedItemId: formData.selectedItemId
});
this.$store.dispatch('setStep', this.step + 1)
this.$router.push('/shop/buyer')
},
In the store.js I check that the info arrives correctly in the 'formInfo()' method and I save it in a declared state class property and I set up a get of the info stored in the state.
export default new Vuex.Store({
state: {
step: 0,
idToken: null,
items: null,
shops: null,
estimations:null,
owner: {
ownerCivility: '',
ownerLastname: '',
ownerFirstname: '',
ownerAddressFirstLine: '',
ownerAddressSecondLine: '',
ownerAddressThirdLine: '',
ownerPostalCode: '',
ownerCity: '',
ownerPhone: '',
ownerEmail: '',
country: 'FR'
},
fisrtStepInfo: {
}
},
actions: {
formInfo({commit, dispatch}, authData) {
console.log(authData)
this.fisrtStepInfo = authData
console.log(this.fisrtStepInfo)
}
},
getters: {
formInfoFirstStep (state) {
console.log(state)
return state.fisrtStepInfo
}
}
Finally, in the component where I want to show that info in my html, I set in the 'computed' section of my script the call to getter previously declared in the store.js.
export default {
data() {
return {
step: 2,
civil: '',
name: '',
lastName: '',
email: '',
adresse: '',
phone: '',
postalCode: '',
submitted: false,
}
},
components:{
},
computed: {
firstFormInfo() {
console.log('firstforminfo')
return !this.$store.getters.formInfoFirstStep
},
}
}
</script>
But at this point, it doesn't even go through the getter in my 'computed' section.
What am I doing wrong?
Thank you in advance for your time and help.
Actions are similar to mutations, the differences being that: Instead of mutating the state, actions commit mutations.
You need to commit a mutation instead of directly altering state:
state: {
...
fisrtStepInfo: {}
},
mutations: {
setStepInfo: (state, data) => state.fisrtStepInfo = data;
}
actions: {
formInfo({commit, dispatch}, authData) {
console.log(authData)
commit('setStepInfo', authData)
console.log(this.fisrtStepInfo)
}
},
getters: {
formInfoFirstStep (state) {
console.log(state)
return state.fisrtStepInfo
}
}
I'm using vuex to manage the state in my application and doing one way binding with my form.
<script>
import { mapGetters } from 'vuex'
import store from 'vuex-store'
import DataWidget from '../../../../uiComponents/widget'
export default {
data () {
return {
isEdit: false,
msg: {
id: 0,
content: '',
isEnabled: false
}
}
},
components: {
DataWidget
},
computed: mapGetters({
messageId: 'messageId',
messageContent: 'messageContent',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
methods: {
onSave () {
store.dispatch('saveMessage', this.msg, { root: true })
if (this.isMessageValid) {
this.isEdit = !this.isEdit
}
}
},
created () {
this.msg.id = this.messageId
this.msg.content = this.messageContent
this.msg.isEnabled = this.isMessageEnabled
}
}
</script>
<b-form-textarea id="content" v-model="msg.content" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
On load, the values on created() are not binded until I perform an action on the page or refresh the page.
I have tried mounted () hooked same thing.
My Vuex store (Message Module) looks like this:
const state = {
messageId: 0,
messageContent: '',
isMessageEnabled: false,
isMessageValid: true
}
const getters = {
messageId: state => state.messageId,
messageContent: state => state.messageContent,
isMessageEnabled: state => state.isMessageEnabled,
isMessageValid: state => state.isMessageValid
}
const actions = {
getMessage ({commit, rootGetters}) {
api.fetch('api/Preference/Message', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data)
})
}
}
const mutations = {
[types.MESSAGE_LOAD] (state, payload) {
state.messageId = payload ? payload.id : 0
state.messageContent = payload ? payload.content : ''
state.isMessageEnabled = payload ? payload.enabled : false
}
}
export default {
state,
getters,
actions,
mutations
}
and I have a global action (action.js) the gets multiple data:
export const loadSetting = ({ commit, rootGetters }) => {
api.fetchAsync('api/Preference/all', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data.message)
commit(types.HELPDESK_LOAD, data.helpDesk)
commit(types.VOLUME_LOAD, data.volumes)
commit(types.DOWNLOAD_LOAD, data.downloadService)
})
}
My api call:
async fetchAsync (url, token = '') {
let data = await axios.get(HOST + url, {
headers: {
'Authorization': 'bearer ' + token
}
})
return data
}
The problem is your'e calling an async method in Vuex but in the created method, you're treating it like a sync operation and expect to get a value.
You need to use the computed properties you created since they are reactive and will update on every change. In order to make the computed writeable change it to be like this:
computed: {
...mapGetters({
messageId: 'messageId',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
messageContent(){
get () {
return this.$store.getters.messageContent
},
set (value) {
//this is just an example, you can do other things here
this.$store.commit('updateMessage', value)
}
}
}
And change the html to use messageContent:
<b-form-textarea id="content" v-model="messageContent" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
For more info refer to this: https://vuex.vuejs.org/en/forms.html
I have this Vuex 2 store:
const datastore = new Vuex.Store({
state: {
socketcluster: {
connection: false,
channels: []
},
selected_offers: []
},
mutations: {
addOffer: function(offer) {
datastore.state.selected_offers.push(offer) // first problem: cannot just use state.offers as it throws an undefined
}
},
getters: {
getOffers: function(){
return datastore.state.selected_offers;
}
}
});
And inside a Vue 2 component I do:
methods: {
clicked: function(){
console.log("Toggle Offer");
if ( datastore.getters.getOffers.filter(function(o){ o.id == this.offer.id } ).length == 0 ) {
// add it
datastore.commit('addOffer', this.offer)
} else {
// TODO remove it
}
}
}
What happens is that when I trigger the method, the store changes as follows:
What am I doing wrong?
This is a simple way to work with vuex pattern, In big applications you should use actions instead of mutating the state directly from the component "like I did ", if so i urge you to read more about vuex.
const store = new Vuex.Store({
state: {
socketcluster: {
connection: false,
channels: []
},
selected_offers: [ "offer1", "offer2"]
},
mutations: {
addOffer: function( state, offer ) {
state.selected_offers.push(offer);
}
},
getters: {
getOffers: function( state ){
return state.selected_offers;
}
}
});
new Vue({
store,
data: function() {
return {
offer: "offer3"
}
},
methods: {
clicked: function() {
if ( this.offers.length === 2 ) {
this.$store.commit('addOffer', this.offer)
} else {
// TODO remove it
}
}
},
computed: {
offers: function() {
return this.$store.getters.getOffers;
}
}
}).$mount( "#app" );
<script src="https://vuejs.org/js/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<div v-for="offer in offers" > {{offer}}</div>
<button #click="clicked"> clicked </button>
</div>
The first parameter passed to a mutation is the state object. So, you're pushing the entire state object to the selected_offers array.
Your mutation method should look like this:
mutations: {
addOffer: function(state, offer) {
state.selected_offers.push(offer)
}
},
Here's the documentation for vuex mutations.