I have posts in my application which a user can either like or dislike: there are two different buttons and each of them shows how many likes/dislikes a post gets. Currently, I am struggling to resolve a bug of this feature. If I quickly click on these buttons, one after another, and then stop, these buttons continue to show this clicking behaviour and there are more requests appear for a short while in my console.
Here is what I have in my component:
<template>
...
<div>
<a style="cursor: default"
#click.pointer="dislikePost(getPost.id)">
<i class="icon-minus"/>{{ getPost.dislikes }} Dislike
</a>
<a style="cursor: default"
#click="likePost(getPost.id)">
<i class="icon-plus" />{{ getPost.likes }} Like
</a>
</div>
...
</template>
And here is what I have in my store:
...
actions: {
likePost({ commit }, postId) {
http
.post(`${config.api}/posts/${postId}/like`)
.then(({ data }) => {
commit('load_post', data);
});
},
dislikePost({ commit }, postId) {
http
.post(`${config.api}/posts/${postId}/dislike`)
.then(({ data }) => {
commit('load_post', data);
});
},
},
mutations: {
load_posts(state, posts) {
state.posts = posts;
},
},
...
Related
but i can't show the comments with v-for and i don't understand why my comment data is not working.
I know there is an error but I can't find it.
My request returns a data , but i can't display it my loop.
Thanks for your help
In store/index.js
state :{
dataComments:[]
}
mutation: {
getComments(state, dataComments) {
console.log(dataComments)
state.dataComments = dataComments;
},
}
action: {
getArticleComments: ({ commit }, dataArticles) => {
return new Promise(() => {
instance.get(`/comment/${dataArticles.article_id}`)
.then(function () {
commit('getComments');
})
.catch(function (error) {
console.log(error)
})
})
},
}
in my views/home.vue
export default {
name: "Home",
data: function () {
return {
articles: [],
comments: [],
}
},
methods: {
getArticleComments(comment) {
this.$store
.dispatch("getArticleComments",comment)
.then((res) => {
this.comments = res.data;
});
},
}
<div class="pos-add">
<button
#click="getArticleComments(article)"
type="button"
class="btn btn-link btn-sm">
Show comments
</button>
</div>
<!-- <div v-show="article.comments" class="container_comment"> -->
<div class="container_comment">
<ul class="list-group list-group comments">
<li
class="
list-group-item
fst-italic
list-group-item-action
comment
"
v-for="(comment, indexComment) in comments"
:key="indexComment"
>
{{ comment.comment_message }}
<!-- {{ comment.comment_message }} -->
</li>
</ul>
</div>
Your action getArticleComments does not return anything and I would avoid changing the action to return data. Instead remove the assignment to this.comments in home.vue
Actions do not return data, they get data, and call mutations that update your store.
Your store should have a getter that exposes the state, in this case the dataComments.
getters: {
dataComments (state) {
return state.dataComments;
}
}
Then in your home.vue you can use the helper mapGetters
computed: {
...mapGetters([
'dataComments'
])
}
You want your views to reference your getters in your store, then when any action updates them, they can be reactive.
More here: https://vuex.vuejs.org/guide/getters.html
As far as I see, you don't return any data in your getArticleComments action. To receive the comments you should return them, or even better, get them from your store data directly.
First make sure that you pass the response data to your mutation method:
getArticleComments: ({ commit }, dataArticles) => {
return new Promise(() => {
instance.get(`/comment/${dataArticles.article_id}`)
.then(function (res) {
commit('getComments', res.data);
})
.catch(function (error) {
console.log(error)
})
})
},
After dispatching you could either return the response data directly or you could access your store state directly. Best practice would be working with getters, which you should check in the vue docs.
getArticleComments(comment) {
this.$store
.dispatch("getArticleComments",comment)
.then((res) => {
// in your case there is no res, because you do not return anything
this.comments =
this.$store.state.dataComments;
});
},
I have a pretty simple view that displays the icons of all characters from a certain game. If I were to visit the URL that displays that view through a router-link, everything works fine and I see the icons, however, if I then refresh the page, the icons disappear.
They also do not render at all if I manually type www.example.com/champions. Why is this happening.
My component:
<template>
<div class='wrapper'>
<div class="champions-container">
<div v-for='champion in champions' class="champion">
<img class='responsive-image' :src="'http://ddragon.leagueoflegends.com/cdn/' + $store.getters.version + '/img/champion/' + champion.image.full" alt="">
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
champions: this.$store.state.fullChampions
}
}
}
</script>
And my Vuex store where the champions are stored:
export default new Vuex.Store({
state: {
version: null,
fullChampions: null
},
mutations: {
version(state, data){
state.version = data.version
},
fullChampions(state, data){
state.fullChampions = data.fullChampions
}
},
actions: {
getVersion({commit}){
return axios.get("http://ddragon.leagueoflegends.com/api/versions.json")
.then((response) => {
commit('version', {
version: response.data[0]
})
})
.catch(function (error) {
console.log(error);
})
},
getFullChampions({commit, state}){
return axios.get("https://ddragon.leagueoflegends.com/cdn/" + state.version + "/data/en_US/championFull.json")
.then((response) => {
commit('fullChampions', {
fullChampions: Object.values(response.data.data)
})
})
.catch(function (error) {
console.log(error);
})
},
These might be because of these issues you encountered.
First: that component is not the one that dispatched your getFullChampions function in your vuex, might be in other component.
Second is that, you are already assigning the value of champions wherein the state fullChampions is not updated.
this.champions: this.$store.state.fullChampions // state.fullChampions might not yet updated.
Try this one might help you
watch: {
'$store.state.fullChampions': function() {
this.champions = this.$store.state.fullChampions
},
}
Last is to to do first a condition above your v-for to prevent the element
<div class="champions-container" v-if=""$store.getters.version>
<div v-for='champion in champions' class="champion">
<img class='responsive-image' :src="'http://ddragon.leagueoflegends.com/cdn/' + $store.getters.version + '/img/champion/' + champion.image.full" alt="">
</div>
</div>
Can you try to add this:
watch: {
$route: function(val, OldVal){
this.champions = this.$store.state.fullChampions;
}
},
after yuor data?
Upd.
If you are calling getFullChampions() action, then you can call it within watcher of my example instead of assigning to this.champions.
I'm using Laravel 5.7 & VueJs 2.5.* ...
I have invoices table, i need to display specific invoice in a new component, so user can see whatever invoice he wants or print that invoice.
I don't know how to do that, i'm just playing around, if you could help me out, i'll be very grateful to you.
<router-link> to the component
<router-link to="/ct-invoice-view" #click="openInvoice(ctInvoice)">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Displaying Customer information here like this:
<div class="col-sm-4 invoice-col">
<address v-for="ctInvoice in ctInvoices" :key="ctInvoice.id">
<strong>Customer Info</strong><br>
Name: <span>{{ ctInvoice.customer.customer_name }}</span>
Invoice view component data() & method{}
data() {
return {
ctInvoices: {},
customers: null
};
},
methods: {
openInvoice(ctInvoice) {
axios
.get("api/ct-invoice/show/" + this.viewInvoice)
.then(({
data
}) => (this.ctInvoices = data.data));
},
Image for Better Understanding
You need to look at Dynamic Route matching: https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
Then you need to use axios.get in invoice views beforeMount function where this.$route.params.id will hold the invoice ID you want to load if the link is applied like so:
<router-link :to="`/ct-invoice-view/${ctInvoice.id}`">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Alternatively...
I suggest not navigating away from the list, it can be irritating for users having filtered the list then returning to it to look at more invoices and having to filter again unless the filter options and current results are sticky
There are a number of ways of doing this and they are lengthy to example, Typically I would make proper use of a modal and the invoice view load the data on display but to get you started a basic in page solution to experiment with, then try adapting in a reusable modal component later:
<button #click="showInvoice = ctInvoice.id">
<i class="fas fa-eye fa-lg text-blue"></i>
</button>
data() {
return {
loading: false,
invoice: {},
customers: null
};
},
computed: {
showInvoice: {
get: function() {
return this.invoice.hasOwnProperty('id');
},
set: function(value) {
if(value === false) {
this.invoice = {};
return;
}
// could check a cache first and push the cached item into this.invoice else load it:
this.loading = true;
axios.get("api/ct-invoice/show/" + value).then(response => {
// you could push the invoice into a cache
this.invoice = response.data;
}).cache(error => {
// handle error
}).finally(() => {
this.loading = false;
});
}
}
}
In view-invoice component have a close button with bind #click="$emit('close')"
Check this article for how $emit works: https://v2.vuejs.org/v2/guide/components-custom-events.html
<div v-if="loading" class="loading-overlay"></div>
<view-invoice v-if="showInvoice" :invoice="invoice" #close="showInvoice = false" />
<table v-else>....</table>
Hide the table when displaying the invoice, experiment with using v-show instead of v-if upon loosing table content state.
Inside your invoice view, property called invoice will contain the invoice data.
Check this article for how to use props: https://v2.vuejs.org/v2/guide/components-props.html
Hint: The #close listens to the $emit('close')
Could also make use of when switching between table and invoice view.
https://v2.vuejs.org/v2/guide/transitions.html
#MarcNewton
I did something like this, it's working for me, can u just review it for me:
<router-link> to the Invoice View component
<router-link v-bind:to="{name: 'ctInvoiceView', params: {id: ctInvoice.id}}">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Getting Data of Specific Invoice ID Like This:
created: function() {
axios
.get("/api/ct-invoice/" + this.$route.params.id)
.then(({
data
}) => {
console.log(data);
this.form = new Form(data);
})
.catch(error => {
console.log(error.response);
});
},
Some background knowledge for my app:
Backend - Python, Flask. The database is currently just nested dictionaries and lists
Frontend - Reactjs
Functions that I will refer to:
FetchPlayers() - uses fetch() to get a array with all the players in it from the backend. It also adds the players to the state using setState().
EditPlayer(playerid) - uses fetch() to POST request the backend to change the information on one of the players.
So currently when the user starts the page, FetchPlayers() is called to get a list of players which will be displayed. Users can also edit these players information through a menu. When the user confirms the edit (through button press), first the app calls EditPlayer(), and then in the line after it calls FetchPlayers(), to update the list of players on the webpage.
The problem is the page is not updating with the new edit. For me the view the new edit I must reload the page. This suggests that FetchPlayers() is somehow being called before EditPlayer(). Does anyone know how I could fix this?
Thanks in advance!
Edit
edit_player(_playerid, _newfirstname, _newlastname, _newclass) {
fetch('http://' + Globals.BaseIP + Globals.PortForBackend + '/api/editplayer', {
//Options for the fetch request
method:'POST',
headers: {
//Header objects
'Accept':'application/json, text/plain, */*',
'Content-type':'application/json'
},
body:JSON.stringify({UserID:UserServiceLocal.UserID(), token:UserServiceLocal.token(), GameID:this.props.gameID, PlayerID:_playerid, Firstname:_newfirstname, Lastname:_newlastname, Class:_newclass}),
mode:'cors'
})
.catch((error) => console.log(error));
}
Code for FetchPlayers
FetchPlayers () {
fetch('http://' + Globals.BaseIP + Globals.PortForBackend + '/api/fetchplayers', {
//Options for the fetch request
method:'POST',
headers: {
//Header objects
'Accept':'application/json, text/plain, */*',
'Content-type':'application/json'
},
body:JSON.stringify({UserID:UserServiceLocal.UserID(), token:UserServiceLocal.token(), GameID:this.props.gameID}),
mode:'cors'
})
.then((res) => res.json())
.then((data) => this.parse_fetch_players_response(data))
.catch((error) => console.log(error))
}
parse_fetch_players_response(data) {
console.log(data['players']);
this.setState({Playerlist:data['players']});
this.ListScrollToBottom();
}
Code that runs when confirm edit
Btn_EditPlayer() {
this.edit_player(this.state.playereditID, this.state.playereditName,
this.state.playereditLastname, this.state.playereditClass);
this.FetchPlayers();
Render function:
return (
<div className="PageFooter" onKeyPress={(event) => this.EnterKeyPress_EditPlayer(event)}>
<Textinput className="FirstNameTextbox" id="playereditName" label="First name" value={this.state.playereditName} onChange={this.textinput_handleChange}/>
<Textinput className="LastNameTextbox" id="playereditLastname" label="Last name" value={this.state.playereditLastname} onChange={this.textinput_handleChange}/>
<Textinput className="ClassTextbox" id="playereditClass" label="Class" value={this.state.playereditClass} onChange={this.textinput_handleChange}/>
<button id='editPlayerButton' className="mdc-button mdc-button--unelevated mdl-button--colored mdc-ripple-surface" onClick={() => this.Btn_EditPlayer()}>Edit Player</button>
<button id="cancel-edit-player-btn" className="mdc-button mdc-button--raised mdl-button--colored mdc-ripple-surface" onClick={() => this.EditPlayer_Cancel_Btn()}>Cancel</button>
</div>
);
List Element Render function:
return (
<div>
<ul className="mdc-list" id="list-container" >
<li role="separator" className="mdc-list-divider"></li>
<li className="mdc-list-item" >
<span className="mdc-list-item__text list_text_firstname">
<b>Firstname</b>
</span>
<span className="mdc-list-item__text list_text_lastname">
<b>Lastname</b>
</span>
<span className="mdc-list-item__text list_text_class">
<b>Class</b>
</span>
<span className="mdc-list-item__graphic" role="presentation">
<i className="material-icons list_edit_icon">edit</i>
<i className="material-icons list_remove_icon">delete</i>
</span>
</li>
<li role="separator" className="mdc-list-divider"></li>
<ListEntry ListItemCSS="selected-list-entry" firstname="This is above" lastname="Butter" class="Jelly" id="1" delete_self={(playerID) => this.delete_item(playerID)} edit_button_clicked={(playerID) => this.Edit_Button_Clicked(playerID)}/>
<ListEntry firstname="Peanut" lastname="Butter" class="Jelly" id="1" delete_self={(playerID) => this.delete_item(playerID)} edit_button_clicked={(playerID) => this.Edit_Button_Clicked(playerID)}/>
{playerListItems}
</ul>
</div>
);
Try removing this.FetchPlayers() from Btn_EditPlayer() and adding it as a callback for edit_player like below:
this.edit_player(this.state.playereditID, this.state.playereditName,
this.state.playereditLastname, this.state.playereditClass, this.FetchPlayers);
edit_player(_playerid, _newfirstname, _newlastname, _newclass, callBack) {
fetch('http://' + Globals.BaseIP + Globals.PortForBackend + '/api/editplayer', {
//Options for the fetch request
method:'POST',
headers: {
//Header objects
'Accept':'application/json, text/plain, */*',
'Content-type':'application/json'
},
body:JSON.stringify({UserID:UserServiceLocal.UserID(), token:UserServiceLocal.token(), GameID:this.props.gameID, PlayerID:_playerid, Firstname:_newfirstname, Lastname:_newlastname, Class:_newclass}),
mode:'cors'
}).then((res) => {
// run your callback (fetchPlayers in this case) only when we know the update is done.
callBack()
}).catch((error) => console.log(error));
}
Edit (typooo)
It's impossible to know without seeing the code, but usually the problem is that you are not storing the information in state or your JSX in the render return isn't using the properties of this.state so it doesn't know to rerender.
So I'm linking in some values to an .hbs-file and most of the values is returned. But for some reason, one of the values is not returning anything. The .hbs:
<nav>
<h2>Working Files</h2>
{{#if snippets }}
<ul>
{{#each snippets}}
<li class={{ this.active }}>{{ this.fileType }}</span>
</li>
{{/each}}
</ul>
{{/if}}
</nav>
And I'm sending them in like this:
router.route("/home/:id")
.get(restrict, function(req, res) {
User.findOne({ user: req.session.Auth.username }, function(error, data) {
Snippet.find({ postedBy: data._id}).exec()
.then(function(data) {
Snippet.find({ _id: req.params.id}).exec()
.then((snippetID) => {
// This is what I send it -------------------------------------------------
let context = {
snippets: data.map(function(snippet) { // Gets the snippet info for nav
return {
name: snippet.title,
fileType: snippet.fileName,
id: "/home/" + snippet._id
};
}),
text: snippetID[0].snippet[0].text, // Gets the snippet-text, and writes it
sessionId: snippetID[0]._id, // For the CRUD to get the id
active: "active"
};
res.render("../views/home", context);
// ------------------------------------
}).catch((err) => {console.log(err)})
}). catch(function(err) {console.log(err)});
});
});
It's the "this.active" that holds no value at all. I've been scratching my head over and over about this, and I can't understand why that value won't follow. All the other values do follow. I've even tried to set the "active"-key to the same value as "id" or "text", but no luck.
Do anyone know what the problem is?