So I have a react component that has a table, the rows are loaded from the server. It also has a Modal component that shows details of the element selected. Right now, when a row is selected, it is set as selected on the state, and the modal receives whatever the selected item is when it is displayed. This leads to some trouble for two reasons: the most recently clicked item is not always set as selected on time and when reloading data from the server (once a minute) there can't be a selected item because there are no items.
What I am trying to do is instead have each item, when clicked, open the modal with its properties.
I tried adding a callback to the table like:
var StripedTable = React.createClass ({
handleClick(index) {
console.log(this.props.callback)
if(this.props.callback)
return this.props.callback(index);
},
render() {
return (
<table className="table table-striped">
<thead>
<tr>
{this.props.headings.map((heading) => {
return <th key={newId()}>{heading}</th>
})}
</tr>
</thead>
<tbody>
{this.props.rows.map((row, index) => {
return (<Tr key={newId()} row={row} onClick={this.handleClick.bind(this, index)}/>)
})}
</tbody>
</table>
)
}
})
This logs the bound method, which is just printing the index to the console, but the printing doesn't happen. Here is the main component's render:
render() {
return (
<Row>
<Col size="12">
<Panel>
<PanelHeading heading="Monitoreo"/>
<PanelBody>
<Row><ProgressBar value={routePercentage} text={delayedText}/>
</Row>
<StripedTable rows={rows} headings={tableHeadings} callback={this.test}/>}
</PanelBody>
</Panel>
</Col>
</Row>
<Modal modalId="stopsModal" cancelText="Cancelar" title="Paradas" width="1100" color="#F7F7F7"
footer={
<ModalButtons>
<button type="button" className="btn btn-flat-primary" data-dismiss="modal">Cerrar</button>
</ModalButtons>
}>
<RouteStops route={this.state.selectedRoute} ref="routeStops"/>
</Modal>
)
}
})
But even though the output from handleClick() is bound(), the function is not called. I removed the modal and the bound method from rows elements, but the result was the same.
EDIT:
Adding the working versions of the table and tr components.
TR:
var Tr = React.createClass ({
propTypes: {
callback: React.PropTypes.func, // receives index of item clicked
index: React.PropTypes.number // index of item
},
handleClick() {
if(this.props.callback)
return this.props.callback(this.props.index);
},
render() {
var cells = this.props.row.map((cell) => {
return <Td key={newId()} text={cell}/>
})
return <tr onClick={this.handleClick}>{cells}</tr>
}
})
Table:
var StripedTable = React.createClass ({
propTypes: {
callback: React.PropTypes.func, // receives index of item clicked
},
render() {
return (
<table className="table table-striped">
<thead>
<tr>
{this.props.headings.map((heading) => {
return <th key={newId()}>{heading}</th>
})}
</tr>
</thead>
<tbody>
{this.props.rows.map((row, index) => {
return (<Tr key={newId()} row={row} index={index} callback={this.props.callback}/>)
})}
</tbody>
</table>
)
}
})
The issue will most likely be in your Tr component, make sure to use the onClick prop you are passing from your StripedTable component
const Tr = React.createClass({
render() {
return (
<tr onClick={ this.props.onClick }>
<td>things</td>
</tr>
);
}
});
Related
I have a table which consists of dropdown menus. Once values are selected from default row further labels are filled. I want to add a new row when I click the '+' button and use the selection process again with separate values.
Things I tried: I added a function as addRows but it gets stuck in an infinite addition of rows. Second, I tried to set state unique using row index but that didn't work out too.
export default class MedTable extends React.Component {
state = {
selectedCondition: "Select Condition",
selectedConditionCarriers: [],
rows: [1],
};
addRow = () => {
var newRows = this.state.rows;
newRows.push("new row");
this.setState({ rows: newRows });
};
render() {
console.log(this.state);
const { carriers, medicalConditions } = Data;
const { selectedConditionCarriers, selectedCondition, rows } = this.state;
return (
<Container fluid className="MedTable">
<Table
dark
hover
striped
bordered
responsive
className="tc animate__animated animate__bounceInUp "
>
<thead>
<tr>
<th>#</th>
<th>Medical Conditions</th>
{carriers.map((item, index) => {
return <th key={index}>{item}</th>;
})}
<th></th>
</tr>
</thead>
<tbody>
{rows.map((item, index) => {
return (
<tr key={index}>
<td>{index}</td>
<td>
<Dropdown as={ButtonGroup}>
<Button
size="sm"
className="ButtonToolbar"
color="info"
onClick={this.addRow()}
>
+
</Button>
<Button variant="success">{selectedCondition}</Button>
<Dropdown.Toggle
split
variant="success"
id="dropdown-split-basic"
/>
<Button
size="sm"
className="ButtonToolbar"
color="danger"
onClick={() => {
this.setState({
selectedCondition: "Select Condition",
selectedConditionCarriers: [],
});
}}
>
x
</Button>
<Dropdown.Menu>
{medicalConditions.map((item, index) => {
return (
<Dropdown.Item
key={index}
onClick={() => {
this.setState({
selectedCondition: item.condition,
selectedConditionCarriers: item.carriers,
});
console.log(this.state);
}}
>
{item.condition}
</Dropdown.Item>
);
})}
</Dropdown.Menu>
</Dropdown>
</td>
{selectedConditionCarriers.map((item, index) => {
return (
<td>
<Label key={index}>{item}</Label>
</td>
);
})}
</tr>
);
})}
</tbody>
</Table>
</Container>
);
}
}
I want to add a new a row once '+' button is clicked
Second, when I try to add a row and select from the dropdown menu all states are selected as same as visible in image
The push method might be causing a side effect to occur. Try either using concat or the spread operator.
addRow = () => {
let length = this.state.rows.length
this.setState({ rows: [...this.state.rows, `row ${length}`] });
};
I'm working on a web-application using the MERN stack that displays a table of clients with their name, email, and phone number. I haven't implemented Redux quite yet, but I'm using 'uuid' to supplement data in the table until I can get the redux store set up. So far I have displaying the the list and adding a client to the list working fine, but I am having trouble with the pesky delete button.
This is the current ClientTable component
import React, { Component } from "react";
import { Table, Container, Button } from "reactstrap";
import { connect } from "react-redux";
import {
getClients,
addClient,
editClient,
deleteClient,
} from "../actions/clientActions";
import PropTypes from "prop-types";
const renderClient = (clients, index, id) => {
return (
<tr key={index}>
<td>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => {
this.setState((state) => ({
clients: state.clients.filter((client) => client.id !== id),
}));
}}
>
×
</Button>
</td>
<td>{clients.name}</td>
<td>{clients.email}</td>
<td>{clients.number}</td>
</tr>
);
};
class ClientTable extends Component {
componentDidMount() {
this.props.getClients();
}
onDeleteClick = (id) => {
this.props.deleteClient(id);
};
render() {
const { clients } = this.props.client;
// const { clients } = this.state;
return (
<Container id="listContainer">
<Table
id="listTable"
className="table-striped table-bordered table-hover"
dark
>
<tr class="listRow">
<thead id="tableHeader">
<tr>
<th id="listActions">Actions</th>
<th id="listName">Name</th>
<th id="listEmail">Email</th>
<th id="listNumber">Number</th>
</tr>
</thead>
<tbody class="listRow">{clients.map(renderClient)}</tbody>
</tr>
</Table>
</Container>
);
}
}
ClientTable.propTypes = {
getClients: PropTypes.func.isRequired,
client: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
client: state.client,
});
export default connect(mapStateToProps, {
getClients,
deleteClient,
addClient,
})(ClientTable);
This is the bit of code that is causing me issues
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => {
this.setState((state) => ({
clients: state.clients.filter((client) => client.id !== id),
}));
}}
>
×
</Button>
When I click the "delete" button I keep getting TypeError: Cannot read property 'setState' of unedefined
I know the error is because of 'this' isn't bound to anything, but I'm uncertain how to bind it within an onClick event if that is even possible or what even to bind it to. I am just lost as to how to approach this problem. (I'm still quite new to React).
If anyone has any ideas it would be greatly appreciated!
move renderClient function to ClientTable, and use it as a method of this class.
class ClientTable extends Component {
componentDidMount() {
this.props.getClients();
}
renderClient = (clients, index) => {
return (
<tr key={index}>
<td>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => this.onDeleteClient(clients.id)}
>
×
</Button>
</td>
<td>{clients.name}</td>
<td>{clients.email}</td>
<td>{clients.number}</td>
</tr>
);
};
onDeleteClick = (id) => {
this.props.deleteClient(id);
};
render() {
const { clients } = this.props.client;
// const { clients } = this.state;
return (
<Container id="listContainer">
<Table
id="listTable"
className="table-striped table-bordered table-hover"
dark
>
<tr class="listRow">
<thead id="tableHeader">
<tr>
<th id="listActions">Actions</th>
<th id="listName">Name</th>
<th id="listEmail">Email</th>
<th id="listNumber">Number</th>
</tr>
</thead>
<tbody class="listRow">{clients.map(this.renderClient)}</tbody>
</tr>
</Table>
</Container>
);
}
}
I have below child component in react which I am rendering on button click event in parent component. Till here I have no problem. Table is getting rendered on page. I have written row click event findDetails() on table. But problem is that rowClick event in not working on row click. Instead of that it get executed when component is rendering on page. I want to execute on rowClick. Below is my code for table component. Also I need to implement pagination as well in same table which I am not sure how to do it.
class Table extends Component {
constructor(props) {
super(props);
this.state = {
};
}
getHeaader = () => {
var tableHeadings = [
"Customer ID",
"Customer Name",
"Address",
"Contact",
];
return tableHeadings.map((key) => {
return <th key={key}> {key.toUpperCase()}</th>;
});
};
getRowsData = (e) => {
return this.props.Data.map((value, index) => {
const {
"Customer_ID",
"Customer_Name",
"Address",
"Contact",
} = value;
return (
<tr
key={CUSTOMER_ID}
onClick={this.findDetails(value)}
>
<td> {CUSTOMER_ID} </td>
<td> {CUSTOMER_NAME} </td>
<td> {Address} </td>
<td> {Contact} </td>
<td>
<button className="btn btn-info">Find Details</button>
</td>
</tr>
);
});
};
findDetails = (value) => {
console.log("in show button", value.count);
if (value["count"] === 0) {
alert("No details for given customer");
}
};
render() {
return (
<div>
<table
id="display-table"
className="table table-bordered table table-hover table table-responsive pagination"
style={{ tableLayout: "fixed" }}
>
<tbody>
<tr>{this.getHeaader()}</tr>
{this.getRowsData()}
</tbody>
</table>
</div>
);
}
}
export default Table;
`
You invoke your onClick in the wrong way. When passing parameters, you have to wrap your function in an anonymous one:
<tr
key={CUSTOMER_ID}
onClick={() => this.findDetails(value)}
>
I'll explain. When passing onClick, React is waiting for a function name (actually a reference), that then it calls, by adding parentheses. If you add them by yourself as you did (onClick={this.findDetails()} ) you invoke the function right away and you pass the RESULT of the function to the onClick listener.
I am new to react and facing some problem while rendering a new component on onClick() on a table cell item.
class Component extends React.Component{
constructor(props) {
super(props);
this.routeChange = this.routeChange.bind(this)
this.state = {
values: []
};
}
routeChange(id) {
console.log(id)
const userAccount = (
<Account />
);
return userAccount;
}
render() {
return (
<div className="classname1">
<table>
<thead className="table-header">
<tr className="table-row">
<th>Account Name</th>
</tr>
</thead>
<tbody>
{this.state.values.map(value => {
return (
<tr className="data-table">
<td className="txt-blue" onClick={() => this.routeChange(value.id)}>{value.name}</td>
</tr>)
})}
</tbody>
</table>
</div>
}
So when I execute the above everything works fine and the table has been rendered properly but when I click on the table cell item then my component is not being rendered. But I can see the console.log() which I have passed in routeChange().
Note: My state values[] is not empty because as here I am only showing the snippet of my code.
You need to pass a reference of a function that calls routeChange function to the onClick function. One way to do this is to use an arrow function.
<td className="txt-blue" onClick={() => this.routeChange(values.value.id)}>{values.value.name}</td>
When you click and the event 'onClick' is triggered, it doesn't expect a return value, meaning that component you are returning is going nowhere.
What you can do to show the 'Account' component is keep a variable, say showAccount, in your state, which initialises as false, and with the method 'routeChange' what you do is change this to true.
I don't quite understand your use case, but something like this could be:
class Component extends React.Component{
constructor(props) {
super(props);
this.routeChange = this.routeChange.bind(this)
this.state = {
values: [],
accountId: null,
showAccount: false
};
}
routeChange(id) {
console.log(id)
/* Commenting this,
const userAccount = (
<Account />
);
return userAccount;
*/
this.setState({showAccount: true, accountId: id})
}
render() {
return (
<div className="classname1">
<table>
<thead className="table-header">
<tr className="table-row">
<th>Account Name</th>
</tr>
</thead>
<tbody>
{this.state.values.map(value => {
return (
<tr className="data-table">
<td className="txt-blue" onClick={() => this.routeChange(value.id)}>{value.name}</td>
</tr>)
})}
</tbody>
</table>
{this.state.showAccount && this.state.accountId &&
<Account id={this.state.accountId} />
}
</div>
}
Anyhow, try to play with your component and see what works best for you. What I suggest may not be useful for you, so just take the concept and adapt it for your own app.
I want to find the id of the row clicked in table.For instance I search for a book with the title 'Mastery' and there are 2 books with the same title but different authors.These books get shown in the table correctly. What I want to do is when I click on a particular book in the table it should open up a modal with the book details in input boxes, however when I click on any of the books the modal pops up with just the details of one of the books.
When I type in the search term ('Mastery') I get two suggestions which is the expected behaviour.
When I click on the suggested search term('Mastery') and hit enter or search button. All the books with that title('Mastery') gets populated in the Table. Also the expected behaviour.
Now when I click on the first instance of a book with title 'Mastery' this is what I get in my modal.
When I click on the second instance. I get this.
You realise that it is the same book that gets shown in the modal.
Expected Behaviour:
I want to be able to click on a book in the table and the book in that row get shown in the modal.
import React, { Component } from 'react';
import './Update.css';
// import Pace from 'react-pace-progress';
//CHILD COMPONENTS
import Search from '../../Search/Search';
import Modal from './Modal/Modal';
const Table = ({ data, openBookDetails }) => (
<table className="table table-hover">
<thead>
<tr className="table-primary">
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">ISBN</th>
<th scope="col">No. Of Copies</th>
</tr>
</thead>
<tbody>
{data.map(row =>
<TableRow key={row._id} row={row} openBookDetails={openBookDetails}/>
)}
{/* Remove key={row.id} inside TableRow because it is not used to set the data in the table */}
</tbody>
</table>
)
const TableRow = ({ row, openBookDetails }) => (
<tr className="table-light" onClick={openBookDetails}>
<th scope="row" >{row.title}</th>
<td >{row.author}</td>
<td >{row.isbn}</td>
<td >24</td>
</tr>
)
class Update extends Component{
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: [],
setOfAllBooks: [],
searchedBooks: [],
isBookDetailsOpen: false,
searchForEmpty: true,
isDataFetching: true,
title: '',
author: '',
isbn: ''
};
}
setTableData = (searchedBook) => {
this.setState({searchedBooks: searchedBook});
}
openBookDetails = () => {
this.setState({ isBookDetailsOpen: true});
console.log(this.state.searchedBooks);
this.setState({ title: this.state.searchedBooks.title});
this.setState({ author: this.state.searchedBooks.author});
this.setState({ isbn: this.state.searchedBooks.isbn});
}
closeBookDetails = () => {
this.setState({ isBookDetailsOpen: false});
}
changeIsSearchForEmpty = () => {
this.setState({ searchForEmpty: !this.state.searchForEmpty });
}
changeIsDataFetching = () => {
this.setState({isDataFetching: !this.state.isDataFetching})
}
render(){
const showHideAlert = this.state.searchForEmpty ? 'alert alert-danger d-none' : 'alert alert-danger d-block';
// const showHideProgress1 = this.state.isDataFetching ? 'progress' : 'progress display-none';
const showHideProgress = this.state.isDataFetching ? 'progress progress-bar progress-bar-striped bg-success progress-bar-animated d-block' : 'progress-bar progress-bar-striped progress-bar-animated d-none';
const style= {
width: "100%",
height: "8px"
}
return(
<div>
{/* Uninstall react-pace-progress if not going to be used */}
{/* {this.state.isDataFetching ? <Pace color="#27ae60" height="0.5px"/> : null} */}
<div style={style}>
<div class={showHideProgress} role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style={style}></div>
</div>
<div className={showHideAlert}>
<strong>Sorry!</strong> You have to type in a search word/phrase.
</div>
<div className='px-3 pt-3'>
<Search
state={this.state}
setTableData={this.setTableData}
changeIsSearchForEmpty={this.changeIsSearchForEmpty}
changeIsDataFetching={this.changeIsDataFetching} />
<Table
data={this.state.searchedBooks}
openBookDetails={this.openBookDetails} />
<Modal
data={this.state.searchedBooks}
isBookDetailsOpen={this.state.isBookDetailsOpen}
closeBookDetails={this.closeBookDetails}
updateBookDetails={this.updateBookDetails}
grabTitle={this.grabTitle}
grabAuthor={this.grabAuthor}
grabISBN={this.grabISBN}
state={this.state} />
</div>
</div>
);
}
}
export default Update;
You're going to need some kind of ID or index on the tr element o your TableRow component. You can accomplish your goal without adding any extra react elements to your code, but your onClick function callback must be able to get the actual value.
If you take a look at the code below:
import React from "react";
import ReactDOM from "react-dom";
const data = [
{ id: "one", firstname: "john", lastname: "smith" },
{ id: "foo", firstname: "peter", lastname: "parker" }
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
clicked_id: null
};
}
onClick = event => {
const id = event.currentTarget.getAttribute("data-rowid");
console.log(id);
this.setState({ clicked_id: id });
};
render() {
return (
<div>
<div>Clicked ID: {this.state.clicked_id}</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody>
{data.map(e => (
<tr key={e.id} data-rowid={e.id} onClick={this.onClick}>
<td>{e.id}</td>
<td>{e.firstname}</td>
<td>{e.lastname}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
you can see that the tr actually has a data-rowid element that is later used by the onClick method to extract the value. You can use other tags, I just chose that one for myself.
Edit to add:
If you want to take a look at the code above working, check out this codesandbox link:
https://codesandbox.io/s/4368l97lqx
Second edit:
You could just refactor your TableRow component to call the openBookDetails prop function with the parameter that you want:
class TableRow extends React.Component {
handleClick = (event) => {
event.stopPropagation();
const { row, openBookDetails } = this.props;
openBookDetails(row._id);
};
render() {
const { row } = this.props;
return (
<tr className="table-light" onClick={this.handleClick}>
<th scope="row">{row.title}</th>
<td>{row.author}</td>
<td>{row.isbn}</td>
<td>24</td>
</tr>
);
}
}
As per your code highlighted below:
<tbody>
{data.map(row =>
<TableRow key={row._id} row={row} openBookDetails={openBookDetails}/>
)}
{/* Remove key={row.id} inside TableRow because it is not used to set the data in the table */}
</tbody>
I think you need to pass the row values to openBookDetails like:
<TableRow key={row._id} row={row} openBookDetails={openBookDetails(row)}/>
and accordingly use the row values sent inside the openBookDetails function
Just wrote a similar code like this
`
<TableBody>
{rows.map((row, index) => (
<TableRow key={row.id} style={{
backgroundColor: deletingId == row.id ? 'salmon' : '',
color: deletingId == row.id ? 'white' : '',
}}>
<TableCell component="th" scope="row">
{index + 1}
</TableCell>
<TableCell align="right">{row.stockName}</TableCell>
<TableCell align="right">{row.stockCount}</TableCell>
<TableCell
align="right">{row.categoryResponseDTOS.length > 0 ? row.categoryResponseDTOS.map(category => {
return category.categoryName + ", "
}) : "---"} </TableCell>
<TableCell>
<ButtonGroup variant="contained" aria-label="outlined primary button group">
<Button color="warning" id={row.id}><EditIcon/> EDIT</Button>
<Button color="error" id={row.id} onClick={(e) => handleDelete(e)}>
<DeleteIcon/> DELETE</Button>
<Button color="success"> <ZoomInSharpIcon/> DETAIL</Button>
</ButtonGroup>
</TableCell>
</TableRow>
))}
</TableBody>
`
if you have a key you check the state you created if equals to it