I have a TodoList where it is possible to change, delete etc. single Todos. When a TodoItem gets altered a Http request is made. I would like to block the submit button and show the user that it is loading. But I am struggling with how to structure the store.
One idea I came up was to add a flag for each action like changing, deleting etc. to every TodoItem, but this solution seems a bit cumbersome:
store : {
todos: {
items: [
{
id: 5,
text: "foo bar",
action: {
changing: true, // Is set after user submitted change until server response
deleting: false // Is set after user clicked delete button until server response
}
}
]
}
}
What is the best practice to store different loading-actions on the same item?
You should store string as the value for your action like:
store : {
todos: {
items: [
{
id: 5,
text: "foo bar",
action: "changing", // or "deleting"
}
]
}
On a side note - you should keep your state as normalised as possible.
You can read about it more on normalizr
Related
I'm using fetch() method for retieve the data from an api, for seo purposes i'm retrieving the same info with asyncData() for use it in the head method.
My question is, there is no way to access the fetch method state inside the head to wait the retrieve of the data and just use one way to retrieve my info so i can remove the asyncdata call?
something like this:
head() {
if (!this.$fetchState.pending) {
return {
title: `${this.post.data.attributes.title}`,
meta: [
{
hid: "description",
name: `${this.event.data.attributes.title} - Estudios Claw`,
content: `${this.event.data.attributes.description}`,
},
],
};
}
},
thanks
In Meteor, one can add additional fields to the root-level of the new user document like so:
// See: https://guide.meteor.com/accounts.html#adding-fields-on-registration
Accounts.onCreateUser((options, user) =>
// Add custom field to user document...
user.customField = "custom data";
return user;
});
On the client, one can retrieve some data about the current user like so:
// { _id: "...", emails: [...] }
Meteor.user()
By default, the customField does not exist on the returned user. How can one retrieve that additional field via the Meteor.user() call such that we get { _id: "...", emails: [...], customField: "..." }? At present, the documentation on publishing custom data appears to suggest publishing an additional collection. This is undesired for reasons of overhead in code and traffic. Can one override the default fields for Meteor.user() calls to provide additional fields?
You have a couple of solutions that you can use to solve this.
Null Publication
Meteor.publish(null, function () {
if (this.userId !== null) {
return Meteor.users.find({ _id: this.userId }, { fields: { customField: 1 } });
} else {
return this.ready();
}
}, { is_auto: true });
This will give you the desired result but will also result in an additional database lookup.. While this is don't by _id and is extremely efficient, I still find this to be an unnecessary overhead.
2.Updating the fields the Meteor publishes for the user by default.
Accounts._defaultPublishFields.projection = { customField: 1, ...Accounts._defaultPublishFields.projection };
This has to be ran outside of any Meteor.startup blocks. If ran within one, this will not work. This method will not result in extra calls to your database and is my preferred method of accomplishing this.
You are actually misunderstanding the documentation. It is not suggesting to populate and publish a separate collection, just a separate publication. That's different. You can have multiple publications/subscriptions that all feed the same collection. So all you need to do is:
Server:
Meteor.publish('my-custom-user-data', function() {
return Meteor.users.find(this.userId, {fields: {customField: 1}});
});
Client:
Meteor.subscribe('my-custom-user-data');
I have a user profile section and Im trying to allow the user to edit their information. I am using vuex to store the user profile data and pulling it into the form. The edit form is located in a child component of the userProfile component - which loads the data save commits it to VUEX.
So I can populate the form with the data from VUEX, but as soon as I change any values in the form, it changes the value in my parent component as well.
I am not committing changes to VUEX until the form is saved, so it means the data is bound two way to VUEX. I was under the impression this was not possible. In this case it is not desired since if the user changes some data, then navigates away without actually clicking "save", the data is VUEX is still changed.
Note, this is a simplified example. Im actually using router view to load the child component, or I would pass the data through props. I have tested loading the edit-profile component directly like it is below, and I have the same issue.
Please see the code below, I can't find why the data is being sent back up to the store. Any help is greatly appreciated.
In the parent, I set retrieve the user data like so:
<template>
<div class="content">
<h1>{{getUserDetails.firstname}} {{getUserDetails.lastname}} </h1>
<edit-profile></edit-profile>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import EditProfile from './Edit.vue';
export default {
data() {
return {
// data
}
},
created () {
this.fetchData();
},
components: {
EditProfile:EditProfile
},
computed: mapGetters([
'getUserDetails'
]),
methods: {
fetchData: function () {
var _this = this;
// ajax call - then
_this.$store.commit('setData', {
name: 'userDetails',
data: response.data.data.userDetails
});
}
}
}
</script>
This loads the results and stores them in the store, and works great.
My store has this:
const store = new Vuex.Store({
state: {
userDetails: {}
},
mutations: {
setData(state, payload){
state[payload.name] = payload.data;
}
},
getters: {
getUserDetails: state => {
return state.userDetails;
}
}
}
Everything here is working.
In my child component with the edit form, I am populating the form like this:
<template>
<form>
<label>Name</label>
<input name="firstname" v-model="profile.firstname">
<input name="lastname" v-model="profile.lastname">
<button v-on:click="save">submit</button>
</form>
</template>
<script>
import {mapGetters } from 'vuex';
export default {
data() {
return {
profile:{}
}
},
watch: {
getUserDetails (newData){
this.profile = newData;
}
},
created (){
this.profile = this.$store.getters.getUserDetails;
},
computed: mapGetters([
'getUserDetails'
]),
methods:{
save (){
var _this = this;
// ajax call to POST this.profile then
_this.$store.commit('setData', {
name: 'userDetails',
data: this.profile
});
}
}
}
</script>
If you are looking for a non binding solution with vuex you can clone the object and use the local version for v-model than on submit commit it.
in your created lifecycle function do this:
created (){
this.profile = Object.assign({}, this.$store.getters.getUserDetails);
},
Why I think it is not working as expected for you: you're receiving an object, binding it to a local property. Then when you change that local property, it's bound by object pointer (/memory address) to the store's object.
Creating a new object and setting the properties on that new object based on the properties of the state's user profile object should do the trick, since the new object would have it's own address in memory, would point to another place...
Illustration:
created (){
// create a new object with {...}
this.profile = {
// assign values to properties of same name
firstName: this.$store.getters.getUserDetails.firstName,
lastName: this.$store.getters.getUserDetails.lastName,
};
},
However if those properties (firstName, lastName) are objects or arrays (anything accessed by pointer to memory address) then this wouldn't work either.
So... what I'd most likely end up doing myself in this situation is something like this:
data() {
return {
firstName: '',
lastName: ''
}
},
This defines local properties. When loading the data, you would populate the local values with profile data you have in the Vuex store.
created() {
let profile = this.$store.getters.getUserDetails;
if (profile.firstName && profile.lastName) {
this.firstName = profile.firstName;
this.lastName = profile.lastName;
}
},
Then, when saving, you use your local variables to update the store's values.
methods: {
save() {
let profile = {
firstName: this.firstName,
lastName: this.lastName
};
// ajax call to POST this.profile then
this.$store.commit('setData', {
name: 'userDetails',
data: profile
});
}
}
I'm writing this from the top of my head, so there might be a bug or typo in here... But I hope at the very least my logic is correct ;-P and clear to you.
Pro: until you're ready to save the edited information, you're not reflecting it anywhere else.
Con: if you'd need to reflect temporary changes (maybe in a User Profile Preview area), this might or might not work depending on your app's structure. You might want to bind or save on #input to a state.temporaryUserProfile object in that case?
I am still new to Vue.js, started using it 2 weeks ago. Hope this is clear and correct :)
The problem is caused by using v-model with mapGetters - this creates the two-way binding you've described. The simple solution is to use value instead:
:value="profile.firstName"
This way the form is only changing the local copy of field and not pushing the changes back to the Vuex store.
#AfikDeri solution is great, but it only create a shallow copy(for example it wont work if you have nested objects, which is common to have), to solve this you may serialize then parse your vuex state object getUserDetails, as follow:
created (){
this.profile = JSON.parse(JSON.stringify(this.$store.getters.getUserDetails));
}
I'm using Iron Router. I have a RouterController that looks something like this:
var loggedInUserController = RouteController.extend({
layoutTemplate: "GenericLayout",
waitOn: function () {
return Meteor.subscribe("TheDataINeed");
}
});
And I have a route defined which uses this controller to wait for the 'TheDataINeed':
Router.route("/myapp", {
name: "Landing",
controller: loggedInUserController,
data: function () {
if(this.ready()){
return {content: "page-landing"};
}
}
});
Now, the problem is the data I am subscribed to is conditional: meaning, depending on the user's role, I publish different data, like so:
if (!Roles.userIsInRole(this.userId, 'subscribed') ) {
return [
myData.getElements({}, { fields: { _id: 1, title: 1}, limit: 5 })
];
} else {
return [
myData.getElements({}, { fields: { _id: 1, title: 1} })
];
}
When the user's role is not 'subscribed', I limit the published data to 5 elements.
The problem is publishing is not reactive, so when the user changes his role for the first time to 'subscribed' and I navigate to my route ("/myapp"), the user still sees the limited number of elements instead of all of them.
Is there a way to manually re-trigger the subscription when I am loading this route? If possible, I'd like to do this without adding new packages to my app.
Not sure about that approach but can you try to set session value in route instead of subscription code. Then in a file on client side where your subscriptions are you can wrap Meteor.subscribe("TheDataINeed") in Tracker.autorun and have a session as a subscription parameter. Every time that session value is changed autorun will rerun subscription and it will return you data based on a new value.
I'm trying to set additional info when setting geoFire object in FB.
Something like:
geoFire.set(uniqueId, [latitude, longitude],{'message': message, 'iconUrl': iconURL, lastUpdate: Firebase.ServerValue.TIMESTAMP}).then(function () {
//some stuff
};
I can do it like this and it mostly works (depends on db roundtrip time):
geoFire.set(uniqueId, [latitude, longitude],).then(function () {
firebaseRef.child(uniqueId).update({'message': message, 'iconUrl': iconURL, lastUpdate: Firebase.ServerValue.TIMESTAMP});
})
but my listener triggers too fast and doesn't capture all the data so it renders without icon path.
I had the same problem, didn't want to solve this with a workaround, especially by introducing the delay so kept digging and found this: https://github.com/firebase/geofire-js/issues/40
This basically means we need to restructure the application, where one collection is dedicated to the GeoFire information, another uses the same key to store attached information. Example from the GitHub.
{
geofire: {
"-J9aZZl94Sx6h": { "g": <geohash>, "l": {} }
"-Jhacf97x4S3h": { "g": <geohash>, "l": {} },
...
},
"locations": {
"-J9aZZl94Sx6h": { <location-data> },
"-Jhacf97x4S3h": { <location-data> }
}
}