Component doesn't pick up changes from vuex store - vue.js

https://codepen.io/bohdanafanasyev/pen/ZgqPWv?editors=1010
Above is the example of the issue I am facing now with the following logic:
1. Load records from firebase
2. Populate state with them
3. Render component that displays state
At this point, all work as expected.
4, Add a new product to the firebase
4.1 On success add product to the state
In a console, we can see that commit has added the product, but the component itself doesn't pick up the change.
Can somebody please suggest what is missing in the logic above.
P.S: I will also appreciate if someone could leave their opinion on a better way to restore the namespaced module state rather then.
Object.assign(state, newState)

Two changes here to make the cart reactive:
Declare cart property in cart/state, so that in your store you have state: {cart: {}};
In your addProduct mutation, change state.cart[productName] = "" to
Vue.set(state.cart, productName, "").
The takeaway is
always declare the property to make it reactive;
if you can't declare the property before hand, use Vue.set to add the property;
For more on reactivity.

Related

Dynamically updating v-combobox items list

Is it possible somehow to update Vuetify v-combobox items as the user is typing? I want to change the list of available items depending on what the users started typing, to create an address input with suggestions from a geolocation API.
This is what I tried: #update:search-input='fetchAddresses'
And in fetchAddresses: this.items = newListOfItems
However, while #update:search-input fires as expected, the combobox list will only be updated after losing focus. Can I somehow trigger it to be updated? (This may very well be an X/Y problem, so any hints about other approaches are welcome)
My current, ugly, hack is to force the whole combobox component to re-render with the current value set, and then refocus on it. (There is an activateMenu() method on the combobox that I could use to make sure the list reopened:
this.$nextTick(() => {
if (this.$refs.addressCombobox) {
this.$refs.addressCombobox.focus()
this.$refs.addressCombobox.activateMenu()
}
})
I'm using Vue 2.
In my case no-filter solved this problem
<v-combobox no-filter ... >

Initially hide first group in Vue-Formulate repeatable group

I'm using Vue-Formulate's Repeatable Groups. For my requirements:
A group is optional to add
If a button is pressed to add a group, then the fields in the group must be validated
The form should not initially show the group; it should show the button to add a group
For example, the desired initial appearance is in the following screenshot, which I generated after clicking the "remove" / X button in the linked codesandbox:
I've mocked this up at codesandbox here: Vue Formulate Group with Button to Start
Is this possible? If so, how?
May 2021 UPDATED WORKAROUND
In #braid/vue-formulate#2.5.2, the workaround below (in Research: A hack that seems to UPDATE: USED TO work) no longer works, using a slot to override the close button, save a ref, and trigger a click does. See also the related feature request at https://github.com/wearebraid/vue-formulate/issues/425.
<script>
export default {
// ... fluff omitted
async mounted() {
await this.$nextTick();
if (!this.hasMessages) {
// See also feature request at https://github.com/wearebraid/vue-formulate/issues/425
this.$refs.closeButton.click();
}
},
};
</script>
<template>
<FormulateInput
type="group"
name="rangeMessages"
:minimum="0"
repeatable>
<!-- See https://vueformulate.com/guide/inputs/types/group/#slots -->
<template #remove="{removeItem}">
<button ref="closeButton" #click.prevent="removeItem"/>
</template>
</FormulateInput>
</template>
Research - Vue-Formulate's Docs
In Vue-Formulate's docs on input with type="group"'s props and slots, there is a minimum prop. I've set that to zero, but that doesn't change the initial appearance. I do see multiple slots, but I'm not quite sure which one to use or if I could use any of them to achieve what I want; it seems like default, grouping, and repeatable might be useful in preventing the initial display of the first group.
Research - Vue-Formulate's Tests
I see that FormulateInputGroup.test.js tests that it('repeats the default slot when adding more', so the default slot is the content that gets repeated. According to the docs, the default slot also receives the index as a slot prop, so that could be useful.
Research - Vue Debugger
The item which I want to initially remove is at FormulateInputGroup > FormulateGrouping > FormulateRepeatableProvider > FormulateRepeatable > FormulateInput:
When I remove the initial item to match the desired initial layout, the group hierarchy changes to:
<FormulateInput><!-- the input type="group" -->
<FormulateInputGroup>
<FormulateGrouping/>
<FormulateAddMore>...</FormulateAddMore>
</FormulateInputGroup>
</FormulateInput>
Based on this change, I would expect that I need to modify FormulateGrouping to get the desired initial appearance, but I haven't found in the source what items are available to me there.
Research: A hack that seems to UPDATE: USED TO work
This hack worked in v2.4.5, but as of 2.5.2, it no longer works. See top of post for an updated workaround.
In the mounted hook, when I first render the form, I can introspect
into the formValues passed to v-model to see if the group lacks an
initial elements that is filled out. If so, then I can make use of a
ref msgs on the FormulateInput of type group to then call
this.$refs.msgs.$children[0].$children[0].removeItem(), which
triggers an initial remove of the (empty) item. This is super hacky,
so I'd prefer a better way, but it kind of works. The only problem is
that it validates the fields when clicking on the button, before any
input has been entered.
This is a fair question, and Vue Formulate used to support the behavior of just using an empty array to begin with, however it became clear that it was confusing to users that their fields would not show up without an empty object [{}] when they bound the model, so a change was made to consider an initial value of an empty array an "empty" field and pre-hydrate it with a value. Once that initial hydration is completed however, you can easily re-assign it back to an empty array and you're good to go. This is easily done in the mounted lifecycle hook:
...
async mounted () {
await this.$nextTick()
this.formData.groupData = []
}
...
Here's a fork of your code sandbox: https://codesandbox.io/s/vue-formulate-group-with-button-to-start-forked-32jly?file=/src/components/Reproduction.vue
Provided solutions weren't working for me but thanks to previous answer I've managed to find this one:
mounted(){
Vue.set(this.formData, "groupData", [])
},
which does same effect as
data(){
formData: {
groupData: [],
},
},
mounted(){
this.formData.groupData = []
},

Vuex | Map state - Scoping issue in beforeMount() on page refresh

I'm having a scoping issues with Vuex mapState. I have a requirement to output the users name locally on the page without it being reactively updated by v-model in my input field (for the same user's name data).
The following code works fine, however if the user refreshes the page the state is lost and returns as undefined.
computed: {
...mapState(["userProfile"]),
},
beforeMount() {
this.localUserProfile.name = this.userProfile.name;
console.log("localUserProfile: " + this.localUserProfile.name);
},
I think what might be happening is that your userProfile data in Vuex is set only after the component has mounted when you directly refresh the page? In that case userProfile.name might still be undefined when you assign it. If this is what happens, you could add a watcher in this component that checks for changes in the userProfile and updates the localProfile if it hasn't been set during the mount.
Also: have you looked at the v-once directive? https://v2.vuejs.org/v2/api/#v-once
This should prevent the username from being updated when the value changes without having to reference a local copy.

VueJS Sibling Component Reload Challenges

I'm having a hard time figuring out how to get a component to reload after a sibling is updated. For instance, when I make a selection in the first component, I want the second component to "refresh" to account for the newly selected "state" data:
<c-select
dataEndpoint="/states.json"
errorMessage="Some error message..."
id="state"
message="Some message"
v-model="form.state"
:v="$v.form.state" />
Has the following dependency, so to speak:
<c-select
:dataEndpoint="`/${form.state}.json`"
errorMessage="Some other error message..."
id="county"
message="This field uses a local data source and **is required**"
v-model="form.county"
:v="$v.form.county" />
Once a state is selected or changed, I need to "dynamically" reload the appropriate endpoint to show the counties for that state in a second component. Right now, the only way I can make this work is with a v-if="form.state hack. But, if a user attempts to change the state again, the changes do not take effect in the "county" component. I would appreciate any help or advice on how to best solve this issue.
Here is a link to my vue codebase in Code Sanbox
Ok. Here is the result:
mounted is executed only ONCE, so after dataEndpoint has changed NO UPDATE action performed, that's why you should add watch to your c-list component and check if the entry has changed - update drop-down list:
watch: {
dataEndpoint() {
this.fetchAndSetJsonEndpoint();
}
},
Here is a working version of your code: https://codesandbox.io/s/64mj19r6zw

Aurelia: Trigger update on one custom element when a value in another custom element changes?

I've just recently asked a question ( Refreshing i18n translated string interpolated values in Aurelia ) regarding how to refresh i18n string interpolated values when a select input field (with language ids) changes. I received a great answer, however now I realized that there was another requirement.
It's not only string interpolated values that needs to be refreshed.
In my page-specific templates I have some select fields (custom elements), which gets their option values from injecting a "service" class. That service is responsible for doing the http fetch request, and returning a promise to the select field (custom element).
Now here's the problem. When the global (language) select field from my site-wide nav-bar custom element changes, and i18n refreshing happens using the notification approach proposed in the link above. How would I then also re-fetch my different select input custom elements inside the template, but with the new language id from the language select in the nav-bar?
The only solution I know so far is to do window.location (router.navigate(sameroute) didn't trigger a refresh) and refresh the current page whenever the language select changes, however that's obviously not a great way to handle this.
I'll try to see if I can put together a plunkr, since all this may sound a little confusing.
#chrismbeckett from in https://gitter.im/Aurelia/Discuss gave me this hint:
"For those types of inter-component syncs, use the EventAggregator.
Pub a 'lang-changed' event and let any component do what it needs to
update itself"
So in nav-bar.js i did this:
let payload = { 'lngId': this.appLngId};
this.eventAggregator.publish('lang_changed', payload);
and in the custom element which were to be refreshed I put this inside the attached() function:
this.eventAggregator.subscribe('lang_changed', payload => {
alert(payload.lngId)
self.myTodosService.getMyTodos(payload.lngId);
.then(function(data){
self.myTodos = data;
})
});