React - Table rows only render after unrelated function call - javascript

I have a weird bug that I've been trying to debug for a few hours now but just can't seem to figure it out. Essentially my table rows won't render unless a function that calls setState is run.
My table is formatted like so:
<table classname="ui inverted table">
<thead>
<tr>
<th>Lobby name</th>
<th>Players</th>
<th>Mode</th>
<th>Difficulty</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{this.renderRow()} //Table rows
</tbody>
</table>
The rows are rendered by this function, that maps over an array of objects:
renderRow = () => {
return games.map(function(val, i){
return(
<tr key={i}>
<td>
{val.name}
</td>
<td>
{val.currentPlayers}/4
</td>
<td>
{val.gameMode}
</td>
<td>
{val.difficulty}
</td>
</tr>
)
})
}
Now here is the weird bug. The rows won't render unless I tap a button which calls createGame. The only thing createGame does is: this.setState({show: true})
The Menu is:
<div classname="ui inverted segment">
<div classname="ui large inverted pointing secondary menu">
<button classname="active item">
Lobby
</button>
<div classname="ui right inverted pointing secondary menu">
<button classname="item menu_item" onclick={this.createGame}>
//Have to click this button to render rows for some reason.
<i classname="plus circle icon"></i>
Create Game
</button>
<div classname="item">
<img classname="ui mini circular image" src={this.state.photoURL}></img>
<span classname="menu_name">{this.state.userName}</span>
</div>
</div>
</div>
</div>
CreateGame for reference as well:
createGame = () => {
this.setState({show: true})
};
It seems like it's really the show attribute in state that's triggering the table rows for some reason, but it's not being conditionally rendered so I don't understand why triggering that state param would cause the rendering. If I manually set show: true in React devtools the table rows render as well.
EDIT: games is being populated like so:
componentDidMount(){
//DB listner, gets all games on component mount, and all new games.
db.collection("games")
.onSnapshot(function(querySnapshot){
querySnapshot.forEach(function(doc){
games.push(doc.data())
});
console.log(games);
});
}

As componentDidMount is only called after the first rendering, your table rows will not be rendered initially. The games array is empty at this point.
It would make sense to move games into the component state here. Thereby automatically updating the state, once the games have been loaded. Remember, a setState will usually trigger a re-render.
componentDidMount(){
//DB listner, gets all games on component mount, and all new games.
db.collection("games").onSnapshot(function(querySnapshot) {
const loadedGames = querySnapshot.map(function(doc) {
return doc.data();
});
// update the state
this.setState({ games: loadedGames });
});
}

Related

Mount a vue component to an element in Nuxt 3

I'm trying to mount a Vue component to an element that is rendered by a v-html directive. The parent Vue component is a table. Every table cell has richtext content (including images).
If there is an image in the richtext, I need to add an existing copyright component, that opens an overlay. So it can't be plain HTML.
The component looks as follows (simplified):
How do I do this?
<script lang="ts" setup>
import { onMounted, ref } from '#imports'
const tableEl = ref<Array<HTMLTableElement>>([])
const imageEls = ref<Array<HTMLImageElement>>([])
onMounted(() => {
const els = tableEl.value.querySelectorAll('p > img')
imageEls.value = Array.from(els) as Array<HTMLImageElement>
imageEls.value.forEach((imageEl) => {
const parent: HTMLParagraphElement = imageEl.parentElement as HTMLParagraphElement
parent.style.position = 'relative' // Up to this point, everything works...
// How do I add my "<CopyrightNotice/>" component here?
})
})
</script>
<template>
<div>
<table>
<tr v-for="row in rows" :key="row.id">
<td ref="tdEls" v-for="col in row.cols" v-html="col.content" :key="col.id" />
</tr>
</table>
</div>
</template>
Rendered, it looks like this:
<div>
<table>
<tr>
<td>
<p>Hello World!</p>
</td>
<td>
<p>Content</p>
<p>
<img src="/link/to/src.jpg" alt="a cat">
</p>
</td>
</tr>
</table>
</div>
Instead of working with DOM using JS it is better idea to render something called vnode.
Your solution can break Vuejs reactivity / virtual DOM.
Follow documentation here: Render Functions & JSX
There is an example with combining HTML elements and Vuejs components.

how to save an object using a button in reactjs

Currently, I have a WebSocket chat messager using react js, I want to have it save the message object when I click on its associated button so that it will pin it. How do I get a button press to save the object it's associated with?
Currently, I have an array of objects that just stack on top of each other like so:
{messages.map(message => (
<>
<tr>
<td>{message.message}</td>
<td><button id="pin_button" type="button" >Pin Message</button></td>
</tr>
</>
))}
What I want to do is have it when I press that button it will save that object and preferably send it to a WebSocket so that other people can see the message that was pinned
You should use state.
const [savedMsgs, setSavedMsgs] = useState([]);
<ul className="saved-msgs">
{savedMsgs.map((msg,i) => <li key={i}>{msg.message}</li>)}
</ul>
<tr>
<td>{message.message}</td>
<td>
<button
id="pin_button"
onClick={() => setSavedMsgs([...new Set([...savedMsgs, message])])}
>
Pin Message
</button>
</td>
</tr>

How to pass value to modal using React.js?

I have a table that returns data after a .data.map().. as shown below:
{this.state.data.map((item, i) => {
return (div>
<tr>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.symbol}</td>
And a <td> in the same <table> and <tr> above that displays my modal with the below code:
<td>
<div>
<a className="btn" href="#open-modal" title="More Details">👁</a>
<div id="open-modal" className="modal-window">
<div>
❌
<div>Update Details</div>
<label>{item.id}</label> //WHERE I WANT TO DISPLAY MY ID
</div><a href={url}></a></div>
</div>
</td>
Now when I want to display the item.id of each particular row after opening the modal, it returns the item.id of only 1 item in the array and not the actual item.id.
So the item.id in <td> is different from the item.id in the modal. It keeps returning the same item.id for every row I click on. How can I have these 2 have the same value?
The modal only reference to your last id at the time you rendered it
I would suggest you have a state to store your id and render it when you open your modal.
Something like:
const [selectedId, setSelectedId] = useState();
<td>
<div>
<a
className="btn"
onClick={() => setSelectedId(item.id)}
href="#open-modal" title="More Details"
>👁</a>
<div id="open-modal" className="modal-window">
<div>
❌
<div>Update Details</div>
<label>{selectedId}</label>
</div><a href={url}></a></div>
</div>
</td>
The 👁 looks scary though.

Vue JS this.$set not updating reactivity

I have a v-for rendered table that has products in it. This table has a column for "active" status where i want the user to be able to click the active button and it becomes inactive or click the inactive button and it becomes active (a toggle switch basically).
I have this implemented by making a POST call to an api route where my status is updated. This works fine.
The problem is that I cannot get vueJS to update the affected object in the array more than once. this.$set works ONE time. If I hit the toggle switch a second time, it no longer works.
This is my table:
<table class="table table-striped fancy-table">
<thead>
<tr>
<th class="text-center"> </th>
<th>Product/Service</th>
<th class="text-center">Status</th>
<th class="text-center">Date Added</th>
<th class="text-center">QTY Sold</th>
<th class="text-center"> </th>
</tr>
</thead>
<tbody>
<tr v-for="(service, index) in services">
<td class="text-center">
<img v-if="service.featured_image" :src="service.featured_image" class="table-thumb">
<img v-else src="/img/default-product.png" class="table-thumb">
</td>
<td>{{service.service_name}}<span class="secondary">${{parseFloat(service.price).toFixed(2)}}</span></td>
<td class="text-center">
<i class="fas fa-circle active"></i>
<i class="fas fa-circle inactive"></i>
</td>
<td class="text-center">{{ service.created_at | moment("dddd, MMMM Do YYYY") }}</td>
<td class="text-center">10</td>
<td class="text-center"><i class="fal fa-edit table-action-btn"></i></td>
</tr>
</tbody>
</table>
This is my method controlling the update:
updateStatus: function(service, index, status) {
// Grab the authorized user
const authUser = JSON.parse(window.localStorage.getItem('authUser'))
// Add a role and refresh the list
this.$http.post('/api/vendor/services/update/' + service.id + '?updateActive=' + status, { }, { headers: { Authorization: 'Bearer ' + authUser.access_token } }).then((response) => {
// update this service
this.$set(this.services, index, response.body);
}).catch(function(error){
console.log(error);
})
}
EDITS:
I've added a :key param to the v-for table, but I'm still getting the same issue. As you can see in my network panel, the first time you click the button, it goes as expected. Posts to the api route with updateActive=0. The second time you click the button it posts to the api route with updateActive=1 and the data is changed server side, but at this point, my object in the services array is not updated, so it now just continually posts with updateActive=1 rather than showing my other button (toggle switch like i'm wanting).
Here's a screenshot of my network panel with 4 clicks on the button.
The problem here might be with a missing key in your v-for
<tr v-for="(service, index) in services">
you could use the key from the index, but in some occasions that may also cause an issue (for example, when removing a single item in an array, the last DOM object will be removed, because the keys shift over)
<tr v-for="(service, index) in services" :key="index">
Ideally you could use something unique from the data like
<tr v-for="(service, index) in services" :key="service.id">
Thanks to #Daniel above, the answer was that I was expecting a number value back (as it is set to an integer in MySQL) and my ajax call was returning that field as a string.

Angular 2 - Pagination next is not triggering the function to load the data for the next page

I am new to Angular 4 and I am stuck in writing the code for pagination.
I am using separate services named "paginationFirst()" and "paginationNext()" to load the data for the first page and next page respectively in a table. So that on clicking the "First" tab , the data from the first service should be loaded in the table through "firstPage()" function and on clicking the "Next" tab, the data from the second service should be loaded in the table through "nextPage()" function.
The data for the first page is loading fine but it is not loading the data for the next page when the next tab is clicked.
Both the services are working fine and displaying the correct data. I do not want to use npm pagination.
Please help in this regard.
HTML:
<div>
<table class="table table-hover">
<thead>
<tr>
<th class="align-right">VALUE</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of calendarTableSelected">
item.value
</tr>
</tbody>
</table>
</div>
<div class="pagination" *ngIf="tableDiv">
<div>
<a href="#" class="active" (click)="firstPage()" >First</a>
</div>
<div>
Prev
</div>
<div>
Next
</div>
<div>
Last
</div>
</div>
component.ts
firstPage(){
this.calendarService.paginationFirst().subscribe(data =>
this.calendarTableSelected = data);
}
nextPage(){
this.calendarService.paginationNext().subscribe(data =>
this.calendarTableSelected = data);
}
Service.ts
public paginationNext(){
return this.http.get(environment.serverUrl+ '/api/nextpage')
.map((responsePage:Response) => responsePage.json())
.catch((err) => {
console.log('Error while getting response from service: '+ err);
return Observable.throw(err)
})
}
public paginationFirst() {
return this.http.get(environment.serverUrl+'/api/firstpage')
.map((resService4:Response) => resService4.json())
.catch((err) => {
console.log('Error while getting response from service: '+ err);
return Observable.throw(err)
})
}
Please click here to see the screen from the developer tools displaying that the service is returning response
Screenshot for the network tab
I have found the solution. Below I am posting my answer.
<div>
<button class="active" (click)="firstPage()" >First</button>
</div>
<div>
<button (click)="previousPage()">Prev</button>
</div>
<div>
<button (click)="nextPage()">Next</button>
</div>
<div>
<button (click)="lastPage()">Last</button>
</div>
In your component, you set the value like this :
this.calendarTableSelected = data
But in your HTML, you iterate like this
*ngFor="let item of calendarTable"
Either you weren't aware of that, or you forgot some piece of code !

Categories

Resources