Find the row you have clicked in a table - reactjs

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

Related

Adding new row in a table and have separate state for each drop-down button

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}`] });
};

How to map JSON data as a table in React

I'm trying to display data after fetching it, but that does not work :
import React, { Component } from "react";
import { Table, Button, ButtonToolbar } from "react-bootstrap";
const URL = "http://localhost:51644/api/";
let passedAthleteId = 0;
let passedAthleteSessionId = 0;
class AthleteTrainingSession extends Component {
constructor(props) {
super(props);
this.state = {
athleteTrainingSession: [],
discussions: [],
warmups: [],
workouts: [],
stretchings: [],
};
}
componentDidMount() {
this.fetchAthleteTrainingSession();
}
componentDidUpdate() {
this.fetchAthleteTrainingSession();
}
fetchAthleteTrainingSession = () => {
fetch(URL + `Coaches/4/Athletes/1/AthleteSessions/4`)
.then((response) => response.json())
.then((data) => {
this.setState({
athleteTrainingSession: data,
});
});
};
render() {
const {
athleteTrainingSession,
discussions,
warmups,
workouts,
stretchings,
} = this.state;
passedAthleteId = this.props.match.params.athleteId;
passedAthleteSessionId = this.props.match.params.athleteSessionId;
this.discussions = this.state.athleteTrainingSession.Discussions;
this.warmups = this.state.athleteTrainingSession.Warmups;
this.workouts = this.state.athleteTrainingSession.Workouts;
this.stretchings = this.state.athleteTrainingSession.Stretchings;
console.log(athleteTrainingSession);
console.log(this.warmups);
return (
<React.Fragment>
<div>
<h2 className="mt-2">
Programme d'entraînement :{" "}
{athleteTrainingSession.TrainingProgramName}
</h2>
<h4>
Séance d'entraînement : {athleteTrainingSession.TrainingSessionName}
</h4>
</div>
<div>
<ButtonToolbar>
<Button variant="primary">Ajouter</Button>
<Button variant="secondary">Discussion</Button>
</ButtonToolbar>
<h4>Échauffement</h4>
<Table className="mt-4" striped bordered hover size="sm">
<thead>
<tr className="d-flex">
<th className="col-6">Exercice</th>
<th className="col-6">Options</th>
</tr>
</thead>
<tbody>
{warmups.map((warm) => (
<tr className="d-flex" key={warm}>
<td className="col-6">{warm.ExerciseName}</td>
<td className="col-6">
<ButtonToolbar>
<Button className="mr-2" variant="info">
Modifier
</Button>
<Button className="mr-2" variant="danger">
Supprimer
</Button>
</ButtonToolbar>
</td>
</tr>
))}
</tbody>
</Table>
</div>
</React.Fragment>
);
}
}
export default AthleteTrainingSession;
athleteTrainingSession contains the fetched data, and warmups is a sub-object for athleteTrainingSession.
When I console.log(warmups), I can see that it does contain data, but I cannot display it in the table.
athleteTrainingSession contains the fetched data, and warmups is a sub-object for athleteTrainingSession.
When I console.log(warmups), I can see that it does contain data, but I cannot display it in the table.
I think you have misconception of using state in component.
You're able to console the warmups because in your code you console.log(this.warmups), but you render the map with this.state.warmups
you should setState all of the data that you get from fetch, i.e:
fetchAthleteTrainingSession = () => {
fetch(URL + `Coaches/4/Athletes/1/AthleteSessions/4`)
.then((response) => response.json())
.then((data) => {
this.setState({
athleteTrainingSession: data,
warmups: data.Warmups,
workouts: data.Workouts,
discussions: data.Discussions,
stretchings: data.Stretchings,
});
});
};
by doing this way, now you can access the warmups data from this.state.warmups then render it
render() {
const {
athleteTrainingSession,
discussions,
warmups,
workouts,
stretchings,
} = this.state;
return (
<React.Fragment>
...
{warmups.map((warm) => (
<tr className="d-flex" key={warm}>
<td className="col-6">{warm.ExerciseName}</td>
<td className="col-6">
<ButtonToolbar>
<Button className="mr-2" variant="info">
Modifier
</Button>
<Button className="mr-2" variant="danger">
Supprimer
</Button>
</ButtonToolbar>
</td>
</tr>
))}
...
</React.Fragment>
)
}

No access to "this"

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

Open Modal from Table Row in React

openBookDetails sets isBookDetailsOpen: true which should change the className in showHideClassName to modal display-block and it should show on the screen but the modal doesn't show. However it appears in Elements in the Development tools
import React, { Component } from 'react';
import './Update.css';
import Search from '../Search/Search';
// import Modal from './Modal/Modal';
const Table = ({ data, openBookDetails }) => (
<table class="table table-hover">
<thead>
<tr class="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}/>
)}
</tbody>
</table>
)
const TableRow = ({ row, openBookDetails }) => (
<tr class="table-light" onClick={openBookDetails}>
<th scope="row" >{row.title}</th>
<td >{row.author}</td>
<td >{row.isbn}</td>
<td >24</td>
</tr>
)
const Modal = ({ closeBookDetails, isBookDetailsOpen, children }) => {
const showHideClassName = isBookDetailsOpen ? 'modal display-block' : 'modal display-none';
return (
<div className={showHideClassName}>
<section className='modal-main'>
{children}
<button
onClick={closeBookDetails}
>
Close
</button>
</section>
</div>
);
};
class Update extends Component{
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: [],
setOfAllBooks: [],
searchedBooks: [],
isBookDetailsOpen: false,
};
this.openBookDetails = this.openBookDetails.bind(this);
this.setTableData = this.setTableData.bind(this);
}
setTableData(searchedBook){
this.setState({searchedBooks: searchedBook})
console.log(this.state.searchedBooks)
}
openBookDetails(){
console.log('openBookDetails')
this.setState({ isBookDetailsOpen: true})
}
closeBookDetails(){
this.setState({ isBookDetailsOpen: false})
}
render(){
return(
<main>
<div class="container">
<Search state={this.state} setTableData={this.setTableData} />
<Table data={this.state.searchedBooks} openBookDetails={this.openBookDetails}/>
<Modal isBookDetailsOpen={this.state.isBookDetailsOpen} closeBookDetails={this.closeBookDetails} />
{/* <Modal /> */}
</div>
</main>
)
}
}
export default Update;

How to pass static data to react component from array

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

Resources