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.
Related
I have a js code that works, but the problem is that when I use it on the main HTML page, for some reason it only works on the first picture, although the LIKE icon is on all the pictures.
I do not understand why it is used only on the first value of the icon and not on the others.
How to make it work on all LIKE icons?
const likeIcon = document.getElementById('like-icon');
const likeCount = document.getElementById('like-count');
likeIcon.onclick = () => {
const newId = likeIcon.getAttribute('data-news');
const url = `/like_news/${parseInt(newId)}/`;
fetch(url, {
method: 'GET',
headers: {
'Content-type': 'applicatin/json'
}
})
.then(response => {
return response.json();
})
.then(data => {
if(data.liked) {
likeIcon.classList.remove('empty-heart');
}
else {
likeIcon.classList.add('empty-heart');
}
likeCount.innerHTML = data.like_count;
})
.catch(error => {
console.log(error);
})
}
HTML CODE
<section class="trend">
<div class="container">
<div class="flex align-center justify-between trend-header">
<h2 class="subtitle">
<i class="ri-fire-line subtitle-icon"></i>
В тренде
</h2>
<button class="btn btn-outline">Посмотреть все</button>
</div>
<div class="trend-content">
{% for new in news|slice:"2" %}
<div class="trend-card">
<img src="{{new.banner.url}}" alt="newsPhoto" class="trend-background" />
<div class="card-header">{{new.category.title}}</div>
<div class="card-bottom">
<h3 class="card-title">
{{new.title}}
</h3>
<div class="card-btn">
<div class="count" id="like-count">{{new.likes.count}}</div>
{% if liked_by %}
<button class="btn-up" class="fa fa-heart">
<li><i id="like-icon" data-news="{{new.id}}" class="fa fa-heart"></i></li>
</button>
{% else %}
<button class="btn-up" class="fa fa-heart empty-heart">
<li><i id="like-icon" data-news="{{new.id}}" class="fa fa-heart empty-heart"></i></li>
</button>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>
Tried to use https://developer.mozilla.org/en/docs/Web/API/Document/querySelectorAll but couldn't do it
const likeIcon = document.querySelectorAll('#like-icon');
const likeCount = document.getElementById('like-count');
likeIcon.forEach(like-icon => {
like-icon.addEventListener("click", () => {
const newId = likeIcon.getAttribute('data-news');
const url = `/like_news/${parseInt(newId)}/`;
fetch(url, {
method: 'GET',
headers: {
'Content-type': 'applicatin/json'
}
})
.then(response => {
return response.json();
})
.then(data => {
if(data.liked) {
likeIcon.classList.remove('empty-heart');
}
else {
likeIcon.classList.add('empty-heart');
}
likeCount.innerHTML = data.like_count;
})
.catch(error => {
console.log(error);
})
});
}
}
And i expected it to work for all pictures that I had on main HTML page
It looks like you're on the right track using querySelectorAll to get all the like icons. However, there is a small mistake in your code when you use likeIcon instead of like-icon in your forEach loop.
Try replacing your JS code with the following, which should work for all like icons:
const likeIcons = document.querySelectorAll('#like-icon');
const likeCounts = document.querySelectorAll('#like-count');
likeIcons.forEach((likeIcon, index) => {
likeIcon.addEventListener('click', () => {
const newId = likeIcon.getAttribute('data-news');
const url = `/like_news/${parseInt(newId)}/`;
fetch(url, {
method: 'GET',
headers: {
'Content-type': 'application/json'
}
})
.then(response => {
return response.json();
})
.then(data => {
if (data.liked) {
likeIcons[index].classList.remove('empty-heart');
} else {
likeIcons[index].classList.add('empty-heart');
}
likeCounts[index].innerHTML = data.like_count;
})
.catch(error => {
console.log(error);
});
});
});
This code should loop through all the like icons and add a click event listener to each of them. When a user clicks on a like icon, the corresponding like count and icon should update based on the server response.
Note that we are using querySelectorAll to get all the like icons and like counts, and then we are looping through them using forEach. We are also using the index of each like icon to update the correct like count and icon.
You need to use class selector instead of ID selector.
While multiple Dom have the same ID, but only the first Dom can be query by document.querySelectorAll or document.querySelector.
Selects an element based on the value of its id attribute. There should be only one element with a given ID in a document.
https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors
<i id="like-icon" data-news="{{new.id}}" class="fa fa-heart like-icon"></i>
const likeIcon = document.querySelectorAll('.like-icon');
// like-icon is not a right variable definition.
likeIcon.forEach(likeIcon => {
likeIcon.addEventListener("click", () => {
...
I have a react page that looks like this:
and right now when creating a new category the post request goes through to the database but the categories is not rendered again to display the new category unless you refresh the page (GET request for all categories on page start up).
SideBar.js
createNewCategory = async (input) => {
console.log("CREATING NEW: ", input);
var response = await fetch("http://localhost:8081/api/categories", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Token": 1234,
Accept: "application/json"
},
body: JSON.stringify({
title: input
})
})
let resp = await response.json();
this.setState({
categories: [...this.state.categories, resp]
})
}
CreateCategory.js
handleNewCategory = (event) => {
event.preventDefault()
this.props.createNewCategory(this.state.input)
this.setState({
input: ''
})
}
render(){
return (
<form onSubmit={this.handleNewCategory} className="new-category-form">
<h4>Create Category</h4>
<input onChange={this.handleInput} className="new-category-input" type="text" value={this.state.input} />
<input className="new-category-input" type="submit" value="Create" />
</form>
)
}
CategoriesContainer.js
function CategoriesContainer(props) {
function renderCategories(){
console.log("PROPS: ", props)
return props.categories.map(category => {
console.log("CATEACH: ", category)
return <Category key={category.id} category={category} />
})
}
return(
<div>
{renderCategories()}
</div>
)
}
At the moment if I create a new category with a name of letters I get the err
Uncaught (in promise) SyntaxError: Unexpected token a in JSON at position 0 sidebar.js:46
and if I create it with numbers I get
Warning: Each child in a list should have a unique "key" prop.
Im still new to react so hopefully Im not completely off the mark here, any ideas?
Fixed it. First off I was using response instead of resp to update the state and I was returning just the name rather than the whole object to the POST request.
In a blog-like Vue.js app I'd like to update list of comments of a post with new comments, when a new comment is successfully posted to the server.
Here is my code:
<div v-for="(post, index) in posts" :key="index" >
<div>{{post.body}}</div>
<div class="comments">
<ul v-for="(c, j) in post.comments" :key="j" class="comment">
<li>{{c.user_id}} : {{c.body}} </li>
</ul>
</div>
<div>
<input type="text" v-on:keyup.enter="submitComment(post)" v-model="post.comment" placeholder=" Add your comment" />
</div>
</div>
And the method:
submitComment(post) {
const formData = new FormData();
formData.append('token', this.token);
formData.append('pid', post.id);
formData.append('body', post.comment);
axios.post(this.BASE_URL + "/api/comment", formData)
.then( (res)=> {
console.log('Updated comments are', res.data.comments);
this.$set(post, 'comment', res.data.comments) ; //Here is the problem
})
.catch( error => {
console.log(error);
});
}
Despite the fact that I receive the updated list of comments, empty results is rendered. How can I fix this?
as #blaz pointed out in the comments above, it looks like the error is from a typo in the $set method call as the property should be the plural comments not comment
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);
});
},
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;
},
},
...