React updating state needs 2 clicks in order to update - javascript

Let me start off by saying that I did look for other topics but I haven't found a solution yet so I'd like to walk through this with you guys.
I have a simple website with a search bar, a search result list and a div where I display the item I click in the result list.
The issue starts when I click an item in the results list. I need to click it twice for it to update the div where I display the item.
What happens when I click an item from the search results list:
const getProductById = (store) => {
if (store == "STORENAME") {
Axios.get(
`http://localhost:3001/get-storename-product-by-id/${productId}`
).then((response) => {
console.log(response.data);
setProductResultList(response.data);
setProductTitle(response.data[0].title);
setProductImg(response.data[0].imageSrc);
setFinalProductId(response.data[0].productId);
});
} else {
Axios.get(`http://localhost:3001/get-storename-product-by-id/${productId}`).then(
(response) => {
console.log(response.data);
setProductResultList(response.data);
setProductTitle(response.data[0].title);
setProductImg(response.data[0].imageSrc);
setFinalProductId(response.data[0].productId);
}
);
}
};
The function fetches all the data linked to the productId of the clicked product (this returns all the historic data I have on the item in an array with objects (1 object for each row)).
How I show the item on the page:
<div className="item">
<div>
<img
src={productImg}
alt={productTitle}
width="250px"
height="250px"
/>
<p className="product-id-span">
Product Id: {finalProductId}
</p>
<p className="m-0">Product name:</p>
<p>{productTitle}</p>
<div className="historical-info">
<span>latest prices:</span>
<div className="table-responsive">
<table class="table text-white">
<thead>
<tr>
<th scope="col">Price</th>
<th scope="col">Date</th>
</tr>
</thead>
<tbody>
{productResultList.map((val, key) => {
let parsedPrice = parseInt(val.price);
let parsedPriceInEuros = parsedPrice / 100;
const finalPrice = new Intl.NumberFormat(
"de-DE",
{ style: "currency", currency: "EUR" }
).format(parsedPriceInEuros);
return (
<tr>
<td>
Price:
{val.store == "STORENAME"
? finalPrice
: val.price}
</td>
<td>{val.date}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
</div>
What I've tried:
I tried to only set state to the productResultList in the getProductById function, and set
the other state when the useEffects note changes to the productResultList.
useEffect(() => {
setProductTitle(productResultList[0].title);
setProductImg(productResultList[0].imageSrc);
setFinalProductId(productResultList[0].productId);
}, [productResultList]);
Could someone perhaps explain what I'm doing wrong or what's the right way to do this?
Note:
I changed the real store names to STORENAME because it's not neccessary in this example.
resultList = the list with results when I search, productResultList is the list with objects of the clicked product.

So the solution to this problem was fairly different to what I expected.
The function that initializes getProductById() sets state to productId first so I can use that in my request as you can see.
Because setting state is asynchronious, the productId was not available on the first request.
I fixed this by passing the productId as a parameter to the getProductById function so it does not have to wait for the state to be changed.

Related

javascript react setting a state from a value from a map

const[deleteId,setDeleteId] = useState();
const PatientDeleteVarifiToggle = e =>{
setDeleteId(e.target.value)//<<<<<--------------------------------THIS
setDeleteState(true)
}
return (
<div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Doctor</th>
<th>Emergancy Contact</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{
allPatients.map((patient, i)=>{
return(
<tr>
<td key={i}>{patient.firstName} {patient.lastName}</td>
<td key={i}>{patient.doctor}</td>
<td key={i}>{patient.emergancyContact}</td>
<td><Link to={`/patientDetails/${patient._id}`}>Details</Link> |
<p key={i} value={patient._id} onClick={PatientDeleteVarifiToggle}>Remove</p></td> //<<<<<<------------------------THIS
</tr>
)
})
}
</tbody>
</table>
I need to assign the deleteId state to the id of the patient that I'm mapping. I know that patient._id contains the id I want because I tested it by displaying it inside a p tag
<p key={i} value={patient._id} onClick={PatientDeleteVarifiToggle}>Remove</p>
You are passing value as an attribute of p tag. So you have to use getAttribute("YOUR ATTR NAME") to access The value
What you are doing
const PatientDeleteVarifiToggle = e =>{
setDeleteId(e.target.value)
setDeleteState(true)
}
Here e.target.value returns undefined so your state is not set correctly
If you want to do the way you are doing then
const PatientDeleteVarifiToggle = e =>{
setDeleteId(e.target.getAttribute("value"))
setDeleteState(true)
}
common and easy way of doing this is
As you have access to patient._id inside your map function. You can setState like this..
<p key={i} value={patient._id} onClick={() => {
setDeleteId(patient._id);
setDeleteState(true);
}}>Remove</p>
You cannot access the value attribute of the p tag using e.target.value, if you need the value you can use getAttribute.
But I would suggest passing the value as an argument to the PatientDeleteVarifiToggle function.
I've removed the value attribute on the p tag and passed it as an argument using an inline arrow function.
<p key={i} onClick={() => PatientDeleteVarifiToggle(patient._id)}>
Remove
</p>
I've also slightly modified the PatientDeleteVarifiToggle function.
const PatientDeleteVarifiToggle = (val) => {
setDeleteId(val);
setDeleteState(true); //<<<<<--------------------------------THIS
}
For the onClick event, the value attribute won't work, it will be undefined, but you can instead use the id attribute.
const PatientDeleteVarifiToggle = e =>{
setDeleteId(e.target.id);
setDeleteState(true);
}
...
<p id={patient._id} onClick={PatientDeleteVarifiToggle}>Remove</p>
Additionally, for the mapping, the React key should be on the outer-most returned element, the tr in this case, and since it seems you are mutating the underlying data array, using the array index will lead to reconciliation/rendering issues, use the patient id instead as these are likely unique within the data set and stable (they stay with the data they identify).
{allPatients.map((patient) => {
return (
<tr key={patient._id}>
<td>{patient.firstName} {patient.lastName}</td>
<td>{patient.doctor}</td>
<td>{patient.emergancyContact}</td>
<td>
<Link to={`/patientDetails/${patient._id}`}>Details</Link> |
<p id={patient._id} onClick={PatientDeleteVarifiToggle}>Remove</p>
</td>
</tr>
)
})}

Updating the DOM with arrays JavaScript

I am having a problem when I try to update the DOM with new information coming from an API.
Every time that I click to add new users, the array displays the old, and new information. Ideally, it would update the array first and then display only the new information. I will attach a picture of what is happening. I would like to every time the user click on add new user, the DOM update with only the information of that new user.
HTML part
<table class="table is-fullwidth table is-hoverable table-info">
<thead>
<tr">
<th title="Channel Name" class="has-text-left"> Channel Name </th>
<th title="View per week" class="has-text-right"> View per week </th>
</tr>
</thead>
<tbody id="body-table">
<tr id="tr-table">
</tr>
</tbody>
</table>
script.js
const trline = document.getElementById('body-table')
let usersList = [];
async function getnewUsers(){
const res = await fetch('https://randomuser.me/api')
const data = await res.json()
// create an instance of the results
const user = data.results[0]
// create the new user
const newUser = {
name:`${user.name.first} ${user.name.last}`,
social: Math.floor(Math.random() * 10000 )
}
// update the new user to the database...
addData(newUser)
}
function addData(obj) {
usersList.push(obj)
// update the information on the screen
updateDOM()
}
function updateDOM( providedData = usersList){
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}
addUser.addEventListener('click', getnewUsers)
Result picture:
I found the problem and the solution.
I didn't reset the HTML part to clear before adding a new item. I had to fix the function updateDOM with this: trline.innerHTML = ''
After that, the function works fine.
function updateDOM( providedData = usersList){
trline.innerHTML = '' // clear everything before adding new stuff
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}

How to iterate through table cells and rows in React when I must iterate through multiple data arrays?

I am trying to implement a table but since I have to iterate through multiple arrays for each row, my items wont get rendered in the correct row or are but the height gets messed up since I don't think I implement the table properly. Could someone please help?
<table id="customers">
<thead>
<tr>
<th>Country</th>
<th>Cities</th>
<th>Shops</th>
<th>Map</th>
</tr>
</thead>
<tbody>
<tr>
<td> {this.renderCountries()}</td>
<td> {this.state.renderCities ? this.renderCities() :
<p>{this.state.defaultCity}</p>} </td>
<td> {this.state.renderShops ? this.renderShops() :
<p>{this.state.defaultShopName}</p>} </td>
<td> {this.state.renderMap ? this.renderMap() : null} </td>
</tr>
</tbody>
</table>
in my methods (renderCountries etc), I iterate through the particular array and return another component
renderCountries = () => {
return this.state.countries.map((country, index) => {
return (
<Column
key={index}
data={country}
/>
)
})
};
const Column = ({data}) => (<p> {data}</p>);
The way I have it so far is the only one that at least renders all items in the correct cell, however, it messes up the CSS (the second row, all items get moved about 200px down), I think its because I m not implementing the table properly, but when I change that to anything else, items wont get rendered correctly. If more info is needed please let me know.

Splicing array does not re-render table row Vuejs

I have a user table generated with data from an ajax request. Table roughly looks like this: [Image of Table][1]
When an admin edits a user's username, I want the user's row to re-render with the changes (specifically the users firstname and lastname).
I create the table fine. The models work fine. My parent component receives the edited data just fine in my edit() method, but I can't seem to make my target row re-rendered with my changed data. How can I make my target row update?
I tried the following and it didn't work:
How to update a particular row of a vueJs array list?
https://v2.vuejs.org/v2/guide/list.html#Caveats
I have set key to my row
Tried setting my listOfUsers with Vue.set()
Tried using Vue.set() in place of splice
Here is my parent vue component with the following (I took out irrelevant details):
TEMPLATE:
<table>
<thead>
<tr>
<th>Name</th>
<th>Email Address</th>
<th>Created</th>
<th>Stat</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="(user, index) in listOfUsers" :key="'row' + user._id">
<td>{{user.first_name + ' ' + user.last_name}}</td>
<td>{{user.email}}</td>
<td>{{user.created}}</td>
<td>
<a v-if="user.confirmed" #click="determineButtonClicked(index, 'confirm')"></a>
<a v-else #click="determineButtonClicked(index, 'unconfirm')"></a>
</td>
<td class="buttonCase">
<a #click="determineButtonClicked(index, 'info')"></a>
<a v-if="user.blocked" #click="determineButtonClicked(index, 'blocked')"></a>
<a v-else #click="determineButtonClicked(index, 'block')"></a>
<a v-if="user.enforce_info === 'required'" #click="determineButtonClicked(index, 'enforceInfoActive')"></a>
<a v-else-if="user.enforce_info === 'done'" #click="determineButtonClicked(index, 'enforceInfoChecked')"></a>
<a v-else #click="determineButtonClicked(index, 'enforceInfo')"></a>
<modal v-if="usersList[index]" #toggleClickedState="setState(index)" #editUser="edit(index, $event)" :id="user._id" :action="action"></modal>
</td>
</tr>
</tbody>
</table>
SCRIPT
<script>
export default {
created: function() {
let self = this;
$.getJSON("/ListOfUsers",
function(data){
self.listOfUsers = data;
});
},
data: function() {
return {
listOfUsers: [],
}
},
methods: {
edit(index, update){
let user = this.listOfUsers[index];
user.firstName = update.firstName;
user.lastName = update.lastName;
// this.listOfUsers.splice(index, 1, user)
this.listOfUsers.$set(index, user)
}
}
}
</script>
Thank you for your time and help!
[1]: https://i.stack.imgur.com/lYQ2A.png
Vue is not updating in your edit method because the object itself is not being replaced. Properties of the object do change, but Vue is only looking for the object reference to change.
To force the array to detect a change in the actual object reference, you want to replace the object, not modify it. I don't know exactly how you'd want to go about doing it, but this hacked together fiddle should demonstrate the problem so you can work around it: http://jsfiddle.net/tga50ry7/5/
In short, if you update your edit method to look like this, you should see the re-render happening in the template:
methods: {
edit(index, update){
let currentUser = this.listOfUsers[index];
let newUser = {
first_name: update.firstName,
last_name: update.lastName,
email: currentUser.email,
created: currentUser.created
}
this.listOfUsers.splice(index, 1, newUser)
}
}
You can have a try like this
<script>
export default {
created: function() {
let self = this;
$.getJSON("/ListOfUsers",
function(data){
self.listOfUsers = data;
});
},
data: function() {
return {
listOfUsers: [],
}
},
methods: {
edit(index, update){
let user = this.listOfUsers[index];
user.firstName = update.firstName;
user.lastName = update.lastName;
// this.listOfUsers.splice(index, 1, user)
this.$set(this.listOfUsers,index, user)
}
}
}
</script>

Knockout JS Binding and Filter Data

I have the following Problem
I have this Code to load Json Data from a external Web api
and Show it in my site this works..
but my Problem is
I must FILTER the Data with a Dropdown List
When i select the Value "Show all Data" all my Data must be Show
and when i select the Value "KV" in the Dropdown only the Data
with the Text "KV" in the Object Arbeitsort must Show..
How can i integrate a Filter in my Code to Filter my Data over a Dropdown ?
and the next is how can i when i insert on each Item where in HTML Rendered a Button
to Show Details of this Item SHOWS his Detail Data ?
when i click Details in a Item i must open a Box and in this Box i must Show all Detail Data
of this specific Item ?
$(document).ready(function () {
function StellenangeboteViewModel() {
var self = this;
self.stellenangebote = ko.observableArray([]);
self.Kat = ko.observable('KV');
$.getJSON('http://api.domain.comn/api/Stellenangebot/', function (data) {
ko.mapping.fromJS(data, {}, self.stellenangebote);
});
}
ko.applyBindings(new StellenangeboteViewModel());
});
I'll give this a go, but there's quite a few unknowns here. My suggestions are as follows:
First, create a computed for your results and bind to that instead of self.stellenangebote
self.stellenangeboteFiltered = ko.computed(function () {
// Check the filter value - if no filter return all data
if (self.Kat() == 'show all data') {
return self.stellenangebote();
}
// otherwise we're filtering
return ko.utils.arrayFilter(self.stellenangebote(), function (item) {
// filter the data for values that contain the filter term
return item.Arbeitsort() == self.Kat();
});
});
With regards the detail link, I'm assuming you are doing a foreach over your data in self.stellenangeboteFiltered(), so add a column to hold a link to show more details:
<table style="width:300px">
<thead>
<tr>
<th>Id</th>
<th>Arbeitsort</th>
<th>Details</th>
</tr>
</thead>
<tbody data-bind="foreach: stellenangeboteFiltered">
<tr>
<td><span data-bind="text: Id"> </span></td>
<td><span data-bind="text: Arbeitsort"> </span></td>
<td>Detail</td>
</tr>
</tbody>
</table>
Add a control to show details:
<div data-bind="visible: detailVisible, with: selectedItem">
<span data-bind="text: Position"> </span>
<span data-bind="text: Arbeitsort"> </span>
</div>
In your JS add a function:
// add some observables to track visibility of detail control and selected item
self.detailVisible = ko.observable(false);
self.selectedItem = ko.observable();
// function takes current row
self.showDetail= function(item){
self.detailVisible(true);
self.selectedItem(item);
};
UPDATE
Here's an updated fiddle: JSFiddle Demo

Categories