javascript react setting a state from a value from a map - javascript

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>
)
})}

Related

How to pass a parameter to a function and then use that in function?

I have created a reusable Table Component, Where I pass (headerArray, dataArrayObject, addEditButton ) as an props from parent component.
Table is well created with the code written below.
const Table = ({tableData,headingColumns,addEditButton}) => {
const onEditHandler = (rowDetail) =>{
console.log(rowDetail);
console.log("clicked");
}
const data = tableData.map((row,index) =>{
let rowData = [];
let i = 0;
for(const key in row){
rowData.push({
key:headingColumns[i],
val:row[key]
})
i++;
}
return <tr key = {index}>
{rowData.map((data,index) =>
<td key = {index} data-heading = {data.key}>{data.val}</td>)}
{addEditButton &&
<td>
<button className = "btn btn-outline-dark btn-sm"
onClick ={(row)=>onEditHandler(row)}>
<EditIcon color="secondary"/>
</button>
</td>}
</tr>
})
return (
<div className = "table-container">
<table
className = {tableClass}
style = {{backgroundColor :"rgba(0,0,0,0.4)",fontSize : "12px"}}>
<thead>
<tr>
{headingColumns.map((col,index)=>(
<th key = {index}>{col}</th>
))}
</tr>
</thead>
<tbody>
{data}
</tbody>
</table>
</div>
)
}
But, problem is on onEditHandler, Where I want to pass the data of the particular as an parameter on onClick function, which i call on function I am not receiving the data which i was expecting an object instead recieve a SyntheticBaseEvent as an Value of Parameter passed.
.
While i got function works fine as i also get Clicked console i have mentioned in function.
You need a higher order function like this:
const onEditHandler = rowDetail => nativeEvent => {
console.log(rowDetail);
console.log(nativeEvent);
}
Then in the JSX:
onClick={onEditHandler(row)}
Other solution: just don't give a name to the callback parameter so that row refers to the row you're expecting:
onClick={() => onEditHandler(row)}

How to list individual key values in an object in React

I'm currently creating a table through React where the labels are certain object keys and the table entries are the object values. The set of data I'm using is an array of objects where I use map to get each individual object. Here's how I'm doing it.
{
data.map(graph =>
<div key={graph.ID} className="tables">
<table>
<tr>
<th>{Object.keys(graph)[0]}</th>
<th>{Object.keys(graph)[1]}</th>
<th>{Object.keys(graph)[2]}</th>
<th>{Object.keys(graph)[3]}</th>
<th>{Object.keys(graph)[4]}</th>
<th>{Object.keys(graph)[5]}</th>
<th>{Object.keys(graph)[6]}</th>
<th>{Object.keys(graph)[7]}</th>
<th>{Object.keys(graph)[8]}</th>
<th>{Object.keys(graph)[9]}</th>
<th>{Object.keys(graph)[10]}</th>
<th>{Object.keys(graph)[11]}</th>
<th>{Object.keys(graph)[12]}</th>
<th>{Object.keys(graph)[13]}</th>
</tr>
<tr>
<td>{Object.values(graph)[0]}</td>
<td>{Object.values(graph)[1]}</td>
<td>{Object.values(graph)[2]}</td>
<td>{Object.values(graph)[3]}</td>
<td>{Object.values(graph)[4]}</td>
<td>{Object.values(graph)[5]}</td>
<td>{Object.values(graph)[6]}</td>
<td>{Object.values(graph)[7]}</td>
<td>{Object.values(graph)[8]}</td>
<td>{Object.values(graph)[9]}</td>
<td>{Object.values(graph)[10]}</td>
<td>{Object.values(graph)[11]}</td>
<td>{Object.values(graph)[12]}</td>
<td>{Object.values(graph)[13]}</td>
</tr>
</table>
</div>
)
}
As you can see, each entry on the table is a different key/value, and graph represents each individual object in the array. The problem is that some objects have more than 14 keys so I want to list out every key and value in each object within JSX regardless of the size. Does anyone know how to do that? I've tried using for loops (which is usually how I list out individual keys) but I can't seem to do that within JSX.
Use Array.map() for each row. For the th iterate the keys, and for the td use Object.entries() to get both the keys (for the item's keys) and values:
{
data.map(graph => (
<div key={graph.ID} className="tables">
<table>
<tr>
{
Object.keys(graph)
.map(key => <th key={key}>{key}</th>)
}
</tr>
<tr>
{
Object.entries(graph)
.map(([key, val]) => <th key={key}>{val}</th>)
}
</tr>
</table>
</div>
))
}
I think you could do something like this:
{
data.map(graph => {
return (
<div key={graph.ID} className="tables">
<table>
<tr>
{Object.keys(graph).map(key => {
return <th>{key}</th>
})}
</tr>
<tr>
{Object.keys(graph).map(key => {
return <th>{graph[key]}</th>
})}
</tr>
</table>
</div>
)
})
}
You can just use map() method to do this:
<tr>
Object.keys(graph).map((key) => (<th>key</th>))
</tr>
<tr>
Object.values(graph).map((value) => (<td>value</td>))
</tr>
Note: this will most likely complain about absent key props, so you will have to add a key value to each element. (using the key value of graph object is one option)

Svelte application bug: converting string to boolean in the view fails

In a Svelte app, I have this array of countries:
let countries = [
{
name:"Alegeria",
status: "1"
},
{
name:"Bulgaria",
status :"0"
}
]
Note the status property is a string. I iterate the array this way:
{#if countries.length > 0}
<table class="table">
<thead>
<tr>
<th>Country</th>
<th class="text-right">Status</th>
</tr>
</thead>
<tbody>
{#each countries as c}
<tr>
<td>{c.name}</td>
<td class="text-right"><Switch bind:checked={Boolean(Number(c.status))} /></td>
</tr>
{/each}
</tbody>
</table>
{:else}
<p class="alert alert-danger">No countries found</p>
{/if}
As you can see, I try to convert the value of the status property to a boolean this by using Boolean(Number(c.status)).
Instead of the desired conversion I get the error: Can only bind to an identifier (e.g. foo) or a member expression as the REPL shows.
What am I doing wrong?
As it says in the error, you can only bind to an identifier or member expression - ie a variable.
This is because a bind is a two-way thing, and if you have applied Boolean(Number(()) to it, when someone changes that input, then svelte doesn't know how to undo those functions to 'save' the data back into that variable it's bound to.
If you can't change the status variables to be boolean (better solution, as suggested by other answers), you need to manually do this two-way updating. Drop the bind, just have checked={Boolean(Number(c.status))}, and handle the input's change event to convert from true/false back into "1" or "0", and save that to the status.
Use:
function handleClick(country) {
countries.find(c => c.name == country.name).status = (country.status == "1") ? "0" :"1"
}
and
<Switch checked={Boolean(Number(c.status))} on:change={() => handleClick(c)}/>
See it working in this repl
I think the problem is that the Boolean() function creates a new object, to which you can't bind, because it is never again referenced. You can bind directly to your array of values in countries, using this code:
{#each countries as c, index}
<tr>
<td>{c.name}</td>
<td class="text-right"><Switch bind:checked={countries[index].status} /></td>
</tr>
{/each}
What has changed is that you use the index parameter of the #each loop now to bind to the variable of the countries array. Please be aware that in order for this to properly work, you need to change the status values to true or false. Otherwise it will still work, but the initial value will always be true.
If you just want pass the value down to the Switch component, simply remove the bind: like so:
<td class="text-right"><Switch checked={Boolean(Number(c.status))} /></td>
If you want to update the countries model via the switch component, I suggest to forward the click event and use a simple click handler method, something like this:
function onClick(event, country) {
countries = countries.map(c => {
if (c.name === country.name) {
c.status = event.target.checked ? '1' : '0';
}
return c;
})
}
...
<td class="text-right"><Switch checked={c.status === '1'} on:click={(e) => onClick(e, c)}/></td>
full code on REPL: https://svelte.dev/repl/013286229d3847c1895c4977aee234af?version=3.9.1

Each child list key is unique yet I still get the error in my console. Reactjs

I keep getting Each child in a list should have a unique "key" prop even though patient.timestamp and date is unique. obsHistory is an array and date is a string and date is only render once in a table row td. when I take out obsHistory.map or the other <tr key={date} one at a time the error still persists. I can't get the key prop error resolved not sure why.
renderObsHistory() {
const patientDetails = this.state.observations;
let groupedByDate = patientDetails.reduce((groupArray, patient) => {
groupArray[patient.date] = groupArray[patient.date] || [];
groupArray[patient.date].push(patient);
return groupArray;
}, {});
return Object.entries(groupedByDate).map(([date, obsHistory]) => (
<>
<tr key={date}>
<td>
<strong>{date}</strong>
</td>
</tr>
{obsHistory.map(patient => (
<tr key={patient.timestamp}>
<td>{patient.timestamp}</td>
</tr>
))}
</>
));
}
You should put the key on the Fragment (<>).
Like this :
<React.Fragment key={data}>
<tr>
/* ... */
</tr>
/* ... */
</React.Fragment>

How to correctly setup a conditional expression (if) in a React render()

I want to be able to rendering a certain section of HTML if the condition is true. I am curious about the correct way to setup a conditional if expression within a react render().
I looked it up online and found one way to do this with a inline expression to check if the value is true, if so then it will render the remaining element.
I also setup another way to create variables for the html to be rendered.
Question:
I was unable to wrap both td tags as one for the condition. It looks like this needs to be done per td tag.
Is there a way to do this around both tags or does it require setting up another element around them?
I thought this could also be setup using a => function possibly.
Code for inline render() expression:
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
return (
<tbody>
<tr>
{tdsForObject}
{this.props.objectTypeEditable &&
<td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
}
{this.props.objectTypeEditable &&
<td>
<button onClick={this.handleDelete}>Delete</button>
</td>
}
</tr>
</tbody>
)
}
Code to create buttons outside of render()
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
var updateButton;
var deleteButton;
// if the object can be edited create the update and delete buttons
if (this.props.objectTypeEditable) {
updateButton = (
<td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
);
deleteButton = (
<td>
<button onClick={this.handleDelete}>Delete</button>
</td>
);
}
return (
<tbody>
<tr>
{tdsForObject}
{updateButton}
{deleteButton}
</tr>
</tbody>
)
}
JSX doesn't allow you to return 2 side by side elements. It can only return 1 element. So yeah you can either wrap those 2 inside a single element and use the same verification as you do now.
{this.props.objectTypeEditable &&
<div class="wrapper">
<td>
[...]
</td>
<td>
[...]
</td>
</div>
}
You can also use inline self invoked function and return an array of JSX elements. (the render methods will automatically loop through them and render them). Here I use ES6 arrow function to bind directly the this reference but it is probably doable with a normal function and binding it by hand like this .bind(this)
{(() => {
let elements = [];
if(this.props.objectTypeEditable) {
// push td elements in array
}
return elements;
})()}
you need to use a ternary expression
condition ? expr1 : expr2
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
return (
<tbody>
<tr>
{tdsForObject}
{ this.props.objectTypeEditable
? <td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
: null
}
{ this.props.objectTypeEditable
? <td>
<button onClick={this.handleDelete}>Delete</button>
</td>
: null
}
</tr>
</tbody>
)
}
It is not possible to use more than one inline. React's documentation and examples use ternary operations and recommends it as the default pattern. If you prefer one method over the other thats fine, they are both valid, just stick to one for consistency's sake :)

Categories