Toggle multiple buttons individually inside a react table - reactjs

Inactive State:
Active State:
When I toggle a single button the state of every other button changes which I don't want. Is there any way to control the state of these toggles individually
This is my code for columns to be used in the table:
const COLUMNS = useMemo (() => [
{
Header: "Username",
accessor: "Username",
textAlign: "left",
sortable: false,
},
{
Header: "Status",
accessor: "Status",
textAlign: "center",
Cell: ({ value, cell: { row } }) => (
<div>
<FormControlLabel
control={
<IOSSwitch
checked={status}
onClick={toggler}
name="status"
/>
}
/>
{status ? <span>Active</span> : <span>Inactive</span>}
</div>
),
},
This is my code for the table:
<Table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<Th
{...column.getHeaderProps({
style: { textAlign: column.textAlign },
})}
>
{column.render("Header")}
</Th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row) => {
prepareRow(row);
return (
<Tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<Td
{...cell.getCellProps({
style: { textAlign: cell.column.textAlign },
})}
>
{cell.render("Cell")}
</Td>
);
})}
</Tr>
);
})}
</tbody>
</Table>

You have to assign each button to its individual state for example you can have three states for three buttons.
your states should look like this:
const [firstButton, setFirstButton] = useState(false)
const [secondButton, setSecondButton] = useState(false)
const [thirdButton, setThirdButton] = useState(false)
and in your onClicks you should say:
for example for second button:
onClick={()=>setSecondButton(false)}
and when you want to use this state in your table you should match every button with its state
ps: if your number of buttons are not fix, you should write a function to return an object which keys are button names and values are false ( false is default and this should change when clicks ).
Because of the immutability of states in react.js, you should clone the state and change what you want and leave every other thing as it is then update the whole state.
and use it like:
onClick={()=>setButtonsState({...buttonsState, secondButton:false})}
where buttonsState is like:
const [buttonsState, setButtonsState] = useState(
{
firstButton: false,
secondButton: false,
thirdButton: false
}
)

Related

Problems with react-table using "useSortBy"

I am having some problems on react-table using useSortBy. With no useSortBy the table works fine.
Getting this error:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
var COLUMNS = [
{
Header: 'Data Criação',
accessor: 'createdAt',
Cell: ({value})=> {return value ? format(new Date(value), 'dd/MM/yyyy') : ''},
},
{
Header: 'Nome',
accessor: 'name'
},
{
Header: 'Telefone',
accessor: 'mobile'
},
{
Header: 'Email',
accessor: 'email'
},
{
Header: 'Action',
accessor: (hit)=>{
return <LeadTableAction item={hit} selection={handleLeadDataSelection}/>
}
}
]
const columns = useMemo(()=>COLUMNS, []);
const tableInst = useTable({
columns,
data:props.lead.leadData ? props.lead.leadData : [{}]
}, useSortBy);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = tableInst;
On JSX:
<Table {...getTableProps()}>
<thead>
{headerGroups.map(hg=>{
return (
<tr {...hg.getHeaderGroupProps()}>
{hg.headers.map(h=>{
return (
<th {...h.getHeaderProps(h.getSortByToggleProps())}>
{h.render("Header")}
<span style={{marginLeft: '5px'}}>
{h.isSorted ? (h.isSortedDesc ? <i className="fas fa-sort-down"></i> : <i className="fas fa-sort-up"></i>) : ''}
</span>
</th>
)
})}
</tr>
)
})}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row=>{
prepareRow(row)
return(
<tr {...row.getRowProps()}>
{row.cells.map(cell=>{
return(
<td {...cell.getCellProps()}>
{cell.render('Cell')}
</td>
)
})}
</tr>
)
})}
</tbody>
Can anybody help ?
Problem solved,
I just add a memo in my code:
const data = useMemo(()=>{
return props.lead.leadData ? props.lead.leadData : [{}]
}, [props.lead.leadData]);
This props.lead is the data to fetch direct on the table.
Done! :)
The whole time I tried passing an empty dependency array and my code did not throw any error there. It showed me errors only where I used hooks. I passed the data in the dependency hook in the useMemo array and it worked.
const data = useMemo(() => (employees.data), [employees.data]);

How to pass props to row in react-table

I've been following https://blog.logrocket.com/complete-guide-building-smart-data-table-react/. To apply custom styling depending on cell value, I'm updating the column object like so:
return {
Header: key,
accessor: key,
width: "150",
sortType: "basic",
Cell: ({cell: {value} }) => {
if (value == "Error") {
return <RedCell/>
}
...
}
}
Is it possible instead to apply custom styling to the row containing the cell? I guess a prop would have to be passed down to row, but am just not clear at all on how to do this.
Any pointers would be much appreciated.
For anyone stumbling upon this issue, this has been answered by the author of the react-table library here — https://spectrum.chat/react-table/general/v7-row-getrowprops-how-to-pass-custom-props-to-the-row~ff772376-0696-49d6-b259-36ef4e558821
In your Table component, you pass on any prop (say, rowProps) of your choice for rows —
<Table
columns={columns}
data={data}
rowProps={row => ({
onClick: () => alert(JSON.stringify(row.values)),
style: {
cursor: "pointer"
}
})}
/>
Then to actually use this —
function Table({ columns, data, rowProps = () => ({}) }) {
// Use the state and functions returned from useTable to build your UI
const { getTableProps, headerGroups, rows, prepareRow } = useTable({
columns,
data
});
// Render the UI for your table
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody>
{rows.map(
(row, i) =>
prepareRow(row) || (
<tr {...row.getRowProps(rowProps(row))}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
)
)}
</tbody>
</table>
);
}
Currently you are applying styling to your columns definitions.
In order to apply styling to an entire Row. (eg. make the entire row red if value == "Error"), I would do it in your table UI.
In your UI you are going to be calling prepareRow(row) and then using the row to render the cells.
maybe with : row.cells.map
At this point you could do something different based on the content of a cell row.cells[0]
maybe something like this:
{(row.cells[0].value !== "Error" && row.cells.map(cell =>
{
return (
<TableCell {...cell.getCellProps({ className: cell.column.className })}>
{cell.render('Cell')}
</TableCell>
);
})) || <RedRow />}

react-table Reinitializing on Render

I'm having an issue where react-table (version 7.1.0) seems to be reinitializing any time the page needs to be re-rendered. Using the code below (running example here) as an example, if you were to change the pageIndex value (by switching to a different page), then hit Dummy Button, you can observe that the pageIndex resets back to its default value of 0. The same thing happens if you modify the pageSize in that it automatically resets back to its default value of 10 any time the page has to be re-rendered.
import React, { useState } from "react";
import makeData from "./makeData";
import { useTable, usePagination } from "react-table";
import { ButtonToolbar, Button, Table } from "react-bootstrap";
// Nonsense function to force page to be rendered
function useForceUpdate() {
const [value, setValue] = useState(0);
return () => setValue(value => ++value);
}
export default function App() {
const forceUpdate = useForceUpdate();
const columns = React.useMemo(
() => [
{
Header: "Name",
columns: [
{
Header: "First Name",
accessor: "firstName"
},
{
Header: "Last Name",
accessor: "lastName"
}
]
},
{
Header: "Info",
columns: [
{
Header: "Age",
accessor: "age"
},
{
Header: "Visits",
accessor: "visits"
},
{
Header: "Status",
accessor: "status"
},
{
Header: "Profile Progress",
accessor: "progress"
}
]
}
],
[]
);
const data = React.useMemo(() => makeData(100000), []);
let ArchiveTable = ({ columns, data }) => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize }
} = useTable(
{
columns,
data
},
usePagination
);
return (
<div style={{ textAlign: "center" }}>
<Table striped bordered {...getTableProps()} className="datasets">
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
{column.render("Header")}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr
{...row.getRowProps()}
className={row.isSelected ? "selected" : row.className}
>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</Table>
<div className="pagination" style={{ display: "inline-block" }}>
<ButtonToolbar>
<Button
variant="light"
onClick={() => gotoPage(0)}
disabled={!canPreviousPage}
size="small"
>
<span><<</span>
</Button>
<Button
variant="light"
onClick={previousPage}
disabled={!canPreviousPage}
size="small"
>
<span><</span>
</Button>
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
}}
>
{[5, 10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
<Button
variant="light"
onClick={nextPage}
disabled={!canNextPage}
size="small"
>
<span>></span>
</Button>
<Button
variant="light"
onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage}
size="small"
>
<span>>></span>
</Button>
</ButtonToolbar>
<span>
Page <strong>{pageOptions.length === 0 ? 0 : pageIndex + 1}</strong>{" "}
of <strong>{pageOptions.length}</strong>
</span>
</div>
</div>
);
};
return (
<div className="App">
<ArchiveTable data={data} columns={columns} />
<button onClick={forceUpdate}>Dummy Button</button>
</div>
);
}
I'm at a complete loss for what to do to fix this. What is the proper way to set everything up so that I don't reinitialize react-table every time the page has to be re-rendered? Said another way, if I hit the Dummy Button, I don't want the table to reset back to page 1 with a page size of 10.
Ended up figuring out the answer. The problem was I needed to move the initialization of the table outside of the rendering logic (quite obvious, in hindsight). Basically, I simply created an ArchiveTable function. For anybody who stumbles across this, you can check here for a working example.
function ArchiveTable({ columns, data }) {
// Add all the initialization and table rendering code here
// (everything that was originally part of the ArchiveTable initialization)
}

how to use Table pagination material-ui react js

Hi I use react js and I'm new. I want to use Table pagination material-ui for a table that does not belong to material-ui.For example, I want 2 row per page.I use the reactStrap table. If possible, tell me how to do it.
render() {
const items = this.props.items.map(item => {
return (
<tr key={item.id}>
<td>
<div style={{ width: "110px" }}>
<Button
color="success"
buttonLabel="Edit"
item={item}
updateState={this.props.updateState}
>
Edit
</Button>
<Button color="danger" onClick={() => this.deleteItem(item.id)}>
Del
</Button>
</div>
</td>
<th scope="row">{item.id}</th>
<td>{item.name}</td>
<td>{item.username}</td>
<td>{item.email}</td>
</tr>
);
});
return (
<div
style={{
maxHeight: "600px",
overflowY: "auto"
}}
>
<Table responsive hover>
<thead>
<tr>
<th>Action</th>
<th>ID</th>
<th>name</th>
<th>username</th>
<th>email</th>
</tr>
</thead>
<tbody>{items}</tbody>
</Table>
</div>
);
}
}
You can see my table in CodeSandbox.Thanks in advance for your answers
I prepared an example Only for pagination. You need to work on api calling with the paging parameters. As your api is returning only 10 records, so I set rowsPerPage: 5 so that you can get the reflection of changing page. but default value should be rowsPerPage: 10
state = {
items: [],
page: 0,
rowsPerPage: 5
};
Update:
You need update the url with the state value like below:
let url = `https://reqres.in/api/users?page=${this.state.page +
1}&per_page=${this.state.rowsPerPage}`;
And then call api when you change the page. Please check the below link for code.
Here is the Code Sandbox
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Pagination from '#material-ui/lab/Pagination';
const useStyles = makeStyles((theme) => ({
root: {
"& > * + *": {
marginTop: theme.spacing(2),
},
},
}));
export default function PaginationComponent(props) {
const classes = useStyles();
const [page, setPage] = React.useState(1);
const handleChange = (value) => {
setPage(value);
props.paginationHandler(value);
};
return (
<div className={classes.root}>
<Pagination
count={props.totalCount}
page={page}
variant="outlined"
color="primary"
onChange={handleChange}
/>
</div>
);
}

Find the row you have clicked in a table

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

Resources