I need to give different IDs to table rows, so that when I click on the first row, it'll show "selectedRow: 1" on console. Click on the second row, it'll show "selectedRow: 2"
Thanks in advance!
Here's my code:
<tbody>
{this.testData.map((item, i) => {
return (
<tr onClick={this.handler} key={i} >
<td>{item.name}</td>
<td>{item.age}</td>
<td>{item.address}</td>
</tr>
);
})}
</tbody>
handler=(e)=>{
let selectedRow = e.target.id //doesn't work
console.log("selectedRow:", selectedRow)
}
For that to work you would need to give every tr an id attribute, but it would be better if you just straight up pass that id to the function, like so:
<tr onClick={() => this.handler(item.id)}>
You can also use the index if the items don't have unique IDs.
Now the handler function would receive the id as an argument:
handler = (selectedRow) => {
console.log("selectedRow:", selectedRow)
}
For the tr onClick we can give our id as we like like <tr onClick={this.handler(item.id)}>
and our handler function will be high level function like below, then we don't require to create function each time
handler = id => e => {
console.log(id)
}
Sample code code from sandbox
Related
I am building an app to help me learn react and solidity. I have a table which lists available products on the site. I want customers to be able to input the number of items they would like to buy for each product (each row) separately.
It works if I make all of them change a single variable in my state but stylistically I would like each row to have a unique value. To solve this I created an array to hold each value ( the array is called inputs). The tough part here is that the number of products (rows in the table) can vary so I cant just hard code it.
Rendering the table:
<tbody className="has-text-black-bis">
{indices.map((a) => (
<tr className="rows">
<td ><strong>{this.state.itemNames[a]}</strong></td>
<td ><strong>{this.state.costs[a]} Wei</strong></td>
<td ><strong>{this.state.quantities[a]}</strong></td>
//This row! <td >
Qty: <input type="number" className='table-input' name="inputs" value={this.state.inputs[a]} onChange={()=>this.handleTableInput(a)} />
<button type="button" className='buy-btn' onClick={()=>this.buyItem(a)}> Buy!</button>
</td>
</tr>
))}
</tbody>
Handling the input changes normally -This did not work when passing this.state.inputs[a] as the value as other rows would change as well:
handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
I thought to change the above function to something like this where a (my index) is passed into the function. The problem is that I am not sure how to access the 'event' types like in the above generic function and thus not sure how to access the value that the user has input.
handleTableInput = (ind) => {
const {inputs} = this.state;
inputs[ind] = "value from table input";
this.setState({inputs: inputs})
}
Thank you for any help!
I have a HTML table of clients, and I want to be able to delete one of them thanks to a simple axios delete request. In order to do that, I have to get the ID of the client I want to delete.
So here is what I did :
async function deleteClient(e) {
e.preventDefault();
const id = document.getElementById("client").innerHTML;
console.log(id);
axios
.delete("http://localhost:8080/api/clients/" + id)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
}
The deleteClient function is called onClick in the table. The issue is, it only ever gets the ID of the first row client and never the one from rows below. What can I do to get the right ID and be able to delete other clients ?
For info, here is my HTML table, it is filled dynamically thanks to useEffect hook and a simple axios get request :
<table className="text-white border-2 border-bikeexplogray-light rounded-full">
<tr>
<th>Clients</th>
</tr>
<tr>
<th>ID</th>
<th>Store Name</th>
<th>Username</th>
<th>Email</th>
<th>Phone number</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Website</th>
<th>Delete</th>
</tr>{" "}
{filteredData.map((value, index) => {
return (
<tr key={value.id}>
<td id="client">{value.id}</td>
<td>{value.storeName}</td>
<td>{value.username}</td>
<td>{value.email}</td>
<td>{value.phone}</td>
<td>{value.adress}</td>
<td>{value.zipcode}</td>
<td>{value.city}</td>
<td>{value.website}</td>
<td>
<button onClick={deleteClient}>Delete</button>
</td>
</tr>
Thanks in advance for the help!
The id attribute on the td tag must be unique within the document.
You can use data-id instead, data-* is just a standard way to attach data to the DOM.
In your event handler deleteClient you can use e.currentTarget to access the context.
function deleteClient(e) {
// this will be your button element
const button = e.currentTarget
// search up the DOM tree (each parent) for this selector match
const td = button.closest("tr > td[data-id]")
// this should be the ID from the data-* attribute
console.log(td.dataset.id)
// ...
}
You can easily get the id of the customer you want to delete because you are in a map() function. Each one of your rows have a unical key that is equal to value.id, so you can modify the button as follows to do the trick :
<button onClick={(e) => deleteClient(e, value.id)}>Delete</button>
And don't forget to modify your deleteClient function :
async function deleteClient(e, id) {
e.preventDefault();
//const id = document.getElementById("client").innerHTML;
console.log(id);
axios
.delete("http://localhost:8080/api/clients/" + id)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
}
I have a Table which when you click a td tag that is an plusbutton it should show the details about that row. Something like this:
Right now I am just testing it like this:
props.info.map((l, i) => {
return (
<tr>
<td>{i + 1}</td>
<td>{l.UserId}</td>
<td onClick={props.onShowInfoDetails}>
<MenuPlusButton /></td>
{props.showInfoDetails && (
<DetailsTable />
)
}
</tr>
)
})
where the DetailsTable is the thing i want to render onClick
export const DetailsTable = (props: Props) => {
return (
<tr>
<td>Hello</td>
</tr>
)
}
There is two problems with this. First the DetailsTable renders to the right of the rest of the content and not under it like in the picture. second problem is that when I click it all table rows show the hello not just the one that I clicked. Both of these I can't seem to figure out. The second problem I guess is because it says if props.showEntryDetails is true it renders the DetailsTable and the onClick sets it to true but how do I make it so it's only true for that row that I clicked?
I am trying to create a Table using React and React-Bootstrap that has a custom number of table rows. The table is supposed to store data about player statistics of a certain video game, and based on the video game the statistics may change, thus the number of rows and titles of these rows must be able to dynamically change as well. I wanted to create an array in the state that held the list of current statistics, then map this array to a element using the map function and render the table. However, after trying several approaches I can't get any of the custom input to render. Below is the code :
Class Structure
class Statistics extends Component {
constructor(props) {
super(props)
this.state = {
game: '',
player_names: [],
positions: [],
stat_categories: [
'kills',
'deaths',
'assists'
]
}
}
renderTableRows(array) {
return (
<tr>
<th> NAME </th>
<th> TEAM </th>
<th> POSITION </th>
{ array.map(item => {
console.log(item)
<th key={item}> {item} </th>
})
}
</tr>
)
}
render() {
const columnLength = this.state.player_names.length
const statCols = this.state.stat_categories
return (
<div>
<MyNav url={this.props.location.pathname} />
<Table responsive striped bordered hover>
<thead>
{ this.renderTableRows(statCols) }
</thead>
</Table>
</div>
)
}
}
The console also properly logs the data in state (kills, deaths, assists) -- so the issue is when rendering the element. Any help would be appreciated!
You have no return statement in your map function, inside of renderTableRows.
When using ES6 arrow functions, you can either:
Return data directly without a return statement
(args) => (returnedData);
Or add some logic instead of just returning directly,
(args) => {
// Logic here
return returnedData
}
In the second case you'll need a return statement, because you are logging, if you choose to remove logging, go the first way.
Also, please post the code directly in your question, as using an image makes it less readable and not indexed by search engines.
You have to render each item in separate trs, not as a series of ths
renderTableCols(array) {
return array.map(item => <th>{item}</th>)
}
renderTableColValues(item, cols) {
return cols.map(col => <td>{item[col]}</td>)
}
renderTableRows(array) {
return array.map(item =>
<tr>
<td>{item.name}</td>
<td>{item.team}</td>
<td>{item.position}</td>
{this.renderTableColValues(item, this.cols)}
</tr>
);
}
render() {
return (
<Table>
<thead>
<tr>
<th>NAME</th>
<th>TEAM</th>
<th>POSITION</th>
{this.renderTableCols(this.cols)}
</tr>
</thead>
<tbody>
{this.renderTableRows(items)}
</tbody>
</Table>
);
}
More on tables https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table
I will give you a similar answer of what youre encoutering but its kinda different approach with a excelent solution
So, you are trying to create a dynamic table but youre making table rows static, what i did was letting the table to receive arrays of head and data and then create as many rows or datas that are required.
heres the code
export function objectIntoTableData(object) {
return Object.values(object).map((data, index) => {
return <td key={index}>{data}</td>;
});
}
You must change this index to (value,index) => , thats just my use
tableRows(data) {
return data.map(value => {
return <tr key={value.index}>{objectIntoTableData(value)}</tr>;
});
}
<thead>
<tr>
{head.map((value, index) => {
return <th key={index}>{value}</th>;
})}
</tr>
</thead>
<tbody>{this.tableRows(data)}</tbody>
Rather use a id or index inside your object since the index callback of the map function, its unsafe to use for the keys.
<ReactTableUse
head={["#", "Cell1", "Cell2", "Cell3"]}
data={[{id:1, test:1},{id:2, test:2}]}
/>
Rules:
When your state changes, render method of a class based component will be called.
Question: Who will change the state? will it grow inside the component ? What is your problem ? your are not being able to render anything ? or statistics is not dynamically rendering ? if you want to change it dynamically , you need to change the state first.
I'm building a tbody dynamically in reactjs. I have a function that creates seed data from a loop with this:
accounts.push(
<tr key={i}>
<td>{obj.type}</td>
<td>{obj.name}</td>
<td>{obj.balance}</td>
<td>{obj.id}</td>
</tr>);
It works and i can populate rows in react with <tbody>{accounts}</tbody>. Now I'm trying to dynamically add <td>. I tried the following but it creates a string and ReactJS throws error Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tbody>:
let fields = ['type', 'name', 'balance', 'id'];
data.forEach( function(element, index) {
let fieldsLabel = `<tr key=${++i}>`;
fields.forEach( function(key, index) {
fieldsLabel = fieldsLabel.concat(`<td>${element[key]}</td>`);
});
fieldsLabel = fieldsLabel.concat(`</tr>`);
accounts.push(Array.from(fieldsLabel));
});
I'd like to do something simple like the following but it won't work because of the open <tr> tag:
data.forEach( function(element, index) {
let row = []
row.push(<tr key={++i}>)
fields.forEach( function(key, index) {
row.push(<td>{element[key]}</td>)
});
row.push(</tr>)
accounts.push(row.join(''));
});
How do you dynamically push <td> into an array so it can be used in reactjs like {accounts}?
The following will dynamically add cells to table rows and dynamically create table rows for you to use in your components.
class TbodyRow extends React.Component {
constructor() {
super()
this.createRow = this.createRow.bind(this);
}
createRow(tableRowID, data, dataOrder, cells = []) {
for (let i in dataOrder) {
cells.push(<td key={i}>{data[dataOrder[i]]}</td>)
}
return (<tr key={tableRowID}>{cells}</tr>);
}
render() {
return this.createRow(
this.props.tbodyIdKey,
this.props.rowData,
this.props.dataOrder
);
}
}
Adding data cells (<td>) to a row (<tr>): Put this inside your method that's looping over your array of data (i.e [obj, obj, obj].forEach...):
let fields = ['type', 'name', 'balance', 'id'];
accounts.push(
<TbodyRow key={obj.id}
tbodyIdKey={obj.id}
rowData={obj}
dataOrder={fields}
/>
);
Then use in your tbody as requested:
<tbody>
{accounts}
</tbody>
try this
data.map((item,i) => {
return(
<tr key={++i}>
{fields.length > 0 && fields.map((field,j) => {
return(
<td>{item[field]}</td>
)
})
</tr>
);
});
How do you dynamically push into an array so it can be used in reactjs like {accounts}?
You have the right idea but you're storing a string into fieldsLabel instead of a JSX element. Don't wrap your elements in backticks.
Don't use strings to create elements. Remember that JSX transpiles to React.createElement calls. If you create an element using strings then babel won't transpile that. Refer to the reactjs documentation to learn more about how JSX works.