React onClick taking an extra click to change state - javascript

I have a function that is used to setState in my App.js script. I have passed this as props to my FilteringTable.js component which calls this function when a table row is clicked.
It always takes an extra click to change the state.
How do I fix this problem? Please help
App.js
class App extends React.Component{
state={
data:[]
}
setStateData = rowData =>{
this.setState({
data:rowData
})
};
render(){
return (
<Router>
<div className='App'>
<Header data={this.state.data}/>
<Switch>
<Route path="/filtertable" render={(props)=> <FilteringTable{...props}setStateData={rowData => this.setStateData(rowData)}/>}/>
</Switch>
<Footer data={this.state.data}/>
</div>
</Router>
)
}
}
export default App
FilteringTable.js
export const FilteringTable = (props) => {
return (
<>
<div className="content">
<table className="list-table"
{...getTableProps()}
onClick={()=> {props.setStateData(selectedFlatRows);
console.log("Clicked",selectedFlatRows)}}>
<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 => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>
{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
</div>
</>
)
}
The onClick function in the table component is where the function to setState is used. And it takes an extra click to change the state.
Thank you in advance!!

Try moving this call to useEffect:
useEffect(()=> {props.setStateData(selectedFlatRows)},[selectedFlatRows])

Related

How to show particular table row detail when it is clicked (Reusable Component) in react

I have created a reusable table component but am facing an issue showing detail for the particular row. what I was doing is if the row id is equal to a particular row id then I was trying to show the detail, but in my case for all rows details are visible.
Codesandbox : reusableTableComponent
what I tried:
const TableCustm = ({ TableHeader, dataVal, selectedRowDetail }) => {
const [selectedTableRow, setSelectedTableRow] = useState(null);
console.log("selectedRowDetail", selectedRowDetail);
console.log("selectedTableRow", selectedTableRow);
const data = dataVal.map((row) => {
const rowData = [];
const keys = Object.keys(row);
keys.forEach((key, index) => {
if (index !== 0) {
rowData.push({
key: TableHeader[index],
val: row[key]
});
}
});
return (
<>
<tr onClick={() => setSelectedTableRow(row)}>
{rowData.map((i) => (
<td className="font-lato text-[14px] text-p_black font-semibold py-0">
<div className="d-flex py-2">{i.val}</div>
</td>
))}
</tr>
// **********************detail table Row ********************
<tr>
<td colspan={TableHeader.length}>
<div style={{ background: "#dcdcdc", padding: "20px" }}>
<button className="btn btn-primary">clickme</button>
<hr className="my-2" />
<div className="d-flex ">row detail</div>
</div>
</td>
</tr>
// *******************end detail
</>
);
});
return (
<Table responsive borderless>
<thead>
<tr>
{TableHeader.map((item) => (
<th key={item.id} className="font-normal">
<div className="flex py-[15px]">{item.label}</div>
</th>
))}
</tr>
</thead>
<tbody className="border-0">{data}</tbody>
</Table>
);
};
I can see what you're trying to do. I'm by no means a react specialist, but nothing on Netflix was gripping me, so took up your challenge.
You're almost there, just a few things that are happening that are getting in your way.
I've got this working on codesandbox: https://codesandbox.io/embed/goofy-tereshkova-66p1ki?fontsize=14&hidenavigation=1&theme=dark
1) React is re-rendering the App Component when you click on the row
I'm not sure the best way to get around this, but every time you click the row (even with my step 2), it will re-generate the UUID. I got around this by just hard coding the IDs, as I assume you'll have a better way for generating IDs (or will need to figure out a way to stop the reloading.
But, for now, hardcode the id so you can follow step 2
const testData = [
{
id: 1,
2) Your use of useState between the Parent (App) and Child (TableCustm) components.
Not sure if this is intentional, but you're duplicating selectedTableRow state in both components. What I think you should do is hold the state in the parent (App) component, but pass both the state and the setState method to the child component inside of app.js like so:
<TableCustm
TableHeader={TableHeader}
dataVal={dataValue}
selectedRowDetail={selectedRow}
setRow={setSelectedRow}
/>
So now, inside the child component (TableCustom.js) you can set the state of the selected row like so:
<tr
onClick={(i) => {
setRow(row.id);
}}
>
And then, becaue you're also passing down (from the Parent to Child component) the current selected row selectedRowDetail, you can then conditionally render the row on the screen.
{row.id === selectedRowDetail &&
<tr>
<td colspan={TableHeader.length}>
<div style={{ background: "#dcdcdc", padding: "20px" }}>
<button className="btn btn-primary">clickme</button>
<hr className="my-2" />
<div className="d-flex ">row detail</div>
</div>
</td>
</tr>
}
Also, you might want to add a conditional step when setting the state of the selected row to null, so when you click again it disappears:
<tr
onClick={(i) => {
if (row.id === selectedRowDetail) {
setRow(null);
} else {
setRow(row.id);
}
}}
>
Hope that helps!
When you are working with React you have to understand when to use state and when to use props.
In your scenario you have two approaches:
When you want to show many details at same time, each row manage it owns state.
When you want to show one detail at a time, you must likely want to the parent Table component to manage your state.
It seems you want the approach 2) show one detail at a time, so you have to show it based on the selected row with selectedTableRow === row:
import React, { useState } from "react";
import { Table } from "react-bootstrap";
const TableCustm = ({ TableHeader, dataVal, selectedRowDetail }) => {
const [selectedTableRow, setSelectedTableRow] = useState(null);
console.log("selectedRowDetail", selectedRowDetail);
console.log("selectedTableRow", selectedTableRow);
const data = dataVal.map((row) => {
const rowData = [];
const keys = Object.keys(row);
keys.forEach((key, index) => {
if (index !== 0) {
rowData.push({
key: TableHeader[index],
val: row[key]
});
}
});
return (
<>
<tr
onClick={() =>
setSelectedTableRow(selectedTableRow === row ? null : row)
}
>
{rowData.map((i) => (
<td className="font-lato text-[14px] text-p_black font-semibold py-0">
<div className="d-flex py-2">{i.val}</div>
</td>
))}
</tr>
{selectedTableRow === row && (
<tr>
<td colspan={TableHeader.length}>
<div style={{ background: "#dcdcdc", padding: "20px" }}>
<button className="btn btn-primary">clickme</button>
<hr className="my-2" />
<div className="d-flex ">row detail</div>
</div>
</td>
</tr>
)}
</>
);
});
return (
<Table responsive borderless>
<thead>
<tr>
{TableHeader.map((item) => (
<th key={item.id} className="font-normal">
<div className="flex py-[15px]">{item.label}</div>
</th>
))}
</tr>
</thead>
<tbody className="border-0">{data} </tbody>
</Table>
);
};
export default TableCustm;
CodeSandbox: https://codesandbox.io/s/epic-mcnulty-g0hqhw?file=/src/TableCustm.js
PS: I believe your code need refactoring and I highly recommend you the Code With Mosh videos to start working with React: https://www.youtube.com/watch?v=Ke90Tje7VS0
Refactored code (not ideal yet, but better):
import React, { useState } from "react";
import { Table } from "react-bootstrap";
const TableRow = ({ data, onClickRow, showDetails }) => {
return (
<>
<tr onClick={onClickRow}>
{data.map((item, i) => (
<td
key={i}
className="font-lato text-[14px] text-p_black font-semibold py-0"
>
<div className="d-flex py-2">{item.val}</div>
</td>
))}
</tr>
{showDetails && (
<tr>
<td colSpan={data.length}>
<div style={{ background: "#dcdcdc", padding: "20px" }}>
<button className="btn btn-primary">clickme</button>
<hr className="my-2" />
<div className="d-flex ">row detail</div>
</div>
</td>
</tr>
)}
</>
);
};
const TableCustm2 = ({ TableHeader, dataVal }) => {
const [selectedTableRow, setSelectedTableRow] = useState(null);
return (
<Table responsive borderless>
<thead>
<tr>
{TableHeader.map((item) => (
<th key={item.id} className="font-normal">
<div className="flex py-[15px]">{item.label}</div>
</th>
))}
</tr>
</thead>
<tbody className="border-0">
{dataVal.map((row, index) => (
<TableRow
key={index}
data={TableHeader.map((key) => ({
key: key.label,
val: row[key.label]
}))}
onClickRow={() => {
setSelectedTableRow(index);
}}
showDetails={selectedTableRow === index}
/>
))}
</tbody>
</Table>
);
};
export default TableCustm2;

Rendering multiple componets in a table react

I have a component that renders a table with data from a database, I am experiencing a problem where I cant render the second component called EditButton bottom of the code, I managed to render the 1st component DeleteButton bottom of the code, but when I try to render another button I get errors. The goal is to render another button in the table. Thanks in advance :)
import React, { useState } from 'react';
import { Button } from 'semantic-ui-react';
import { currentloginid } from '../login/loginid.js';
import { deletequestion } from '../question/deletequestion.js';
import { editanswer } from '../answer/editanswer.js';
export const ViewQuestionComponent = () => {
let [state, setState] = useState([]);
const handleViewQuestion = async () => {
try {
const response = await fetch('http://localhost/gotaquestion/api/api.php?action=viewquestion', {
method: 'GET',
credentials: 'include'
});
const data = await response.json();
const result = await data;
setState(data);
} catch (e) {
console.log(e);
}
}
return (
<div>
<ViewQuestion onClick={handleViewQuestion} />
<div id="questions">
<Table rows={state}>
<DeleteButton onClick={deletequestion} />
</Table>
</div>
</div>
);
};
export function ViewQuestion({onClick}) {
return (
<Button onClick={onClick}>View Question</Button>
);
}
export default ViewQuestion;
const Table = ({ rows, setIdTobeDeleted, children }) => (
<table className="ui single line table">
<tbody>
{rows.map((row) => (
<tr key={row.questionid}>
<td>{row.question}</td>
<td>{row.timestamp}</td>
<td>{row.catagories}</td>
<td>{row.answer === null ? "Not Answered" : row.answer}</td>
<td>
{React.cloneElement(children, { questionid: row.questionid })}
</td>
<td>
//I want the EditButton to render there instead on DeleteButton
{React.cloneElement(children, { questionid: row.questionid })}
</td>
<td>{row.questionid}</td>
</tr>
))}
</tbody>
</table>
);
const EditButton = ({ questionid, onClick }) => (
<button
className="ui negative basic button"
onClick={() => onClick(editanswer)}
>Edit Question</button>
);
const DeleteButton = ({ questionid, onClick }) => (
<button
className="ui negative basic button"
onClick={() => onClick(questionid)}
>Delete Question</button>
);
I think in your view you have missed the edit button.
Please consider changing
return (
<div>
<ViewQuestion onClick={handleViewQuestion} />
<div id="questions">
<Table rows={state}>
<DeleteButton onClick={deletequestion} />
</Table>
</div>
</div>
);
to
return (
<div>
<ViewQuestion onClick={handleViewQuestion} />
<div id="questions">
<Table rows={state}>
<DeleteButton onClick={deletequestion} />
</Table>
<Table rows={state}>
<EditButton onClick={editanswer} />
</Table>
</div>
</div>
);

How to pass data from a table row and pass it to another component when it renders?

I have a component that shows a table and one of its columns have a field Actions which has buttons (view, edit, delete etc.). On button click, I need to render another component (component is a popup) and pass the data from the table so that it displays the data in some form which I need to further add.
I have managed to get the current data from its row by passing in onClick. I tried to use state for another component to render but it didn't work out. I'm using Semantic-UI React components to display the button with animations.
Here is the code that has the table,
const MainContent = () => {
const [actions, setActions] = useState(false);
const handleView = (rowData) => {
console.log(rowData);
setActions(true);
if (actions == true) return <ParentView />;
};
....
....
const contents = (item, index) => {
return item.routeChildEntry ? (
<>
<tr key={index}>
<td>{item.appName}</td>
<td></td>
<td>{item.createdTs}</td>
<td>{item.pattern}</td>
<td></td>
<td></td>
<td></td>
<td>
<Button animated onClick={() => handleView(item)}>
<Button.Content visible>View</Button.Content>
<Button.Content hidden>
<Icon name="eye" />
</Button.Content>
</Button>
</td>
</tr>
{item.routeChildEntry.map(routeContents)}
</>
) : (
....
....
....
);
};
return (
<div>
....
{loading ? (
<div className="table-responsive">
<table className="table">
<thead>
<tr>
<th>AppName</th>
<th>Parent_App</th>
<th>Created_Date</th>
<th>Req_Path</th>
<th>Resp_Code</th>
<th>Resp_Content_Type</th>
<th>Resp_Delay</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{data.map((routes, index) => {
return routes.map(contents, index);
})}
</tbody>
</table>
</div>
) : (
....
....
)}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default MainContent;
Below is the component to render on button click,
import React from "react";
import Popup from "reactjs-popup";
import { Icon } from "semantic-ui-react";
const Parent = (props) => {
return (
<Popup trigger={<Icon link name="eye" />} modal closeOnDocumentClick>
<h4>in parent</h4>
</Popup>
);
};
export default Parent;
How can I render the other component and pass data to it on button click?
Data can be passed to other components as props.
For example, if your component is <ParentView />, and the data you are passing is contained in the variable rowData, you can pass it as:
<ParentView dataPassedByAkhil = {rowData}/>
Then in your ParentView component,
export default function ParentView({dataPassedByAkhil}) {
console.log(dataPassedByAkhil);
Alternatively, you can accept the data as props as below
export default function ParentView(props) {
console.log(props.dataPassedByAkhil);
If you want to open and close another popup, you can pass a state just like above.
<PopupComponent open={stateSetForPopupByParent}/>
Example of popup using state.
Updated link above with how to pass data to the dialog from rows of buttons
Here is the full code:
export default function FormDialog() {
const [open, setOpen] = React.useState(false);
const [valueofRow, setValueOfRow] = React.useState();
const handleClickOpen = (value) => {
setValueOfRow(value);
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Button
variant="outlined"
color="primary"
onClick={() => {
handleClickOpen("John");
}}
>
Row 1 - value is John
</Button>
<br />
<br />
<Button
variant="outlined"
color="primary"
onClick={() => {
handleClickOpen("Sally");
}}
>
Row 2 Value is Sally
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="edit-apartment"
>
<DialogTitle id="edit-apartment">Edit</DialogTitle>
<DialogContent>
<DialogContentText>Dialog fired using state</DialogContentText>
<h1>{valueofRow} was clicked and passed from the row</h1>
<TextField
autoFocus
margin="dense"
id="field"
label="some field"
type="text"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="secondary">
Cancel
</Button>
<Button onClick={handleClose} color="primary">
Submit
</Button>
</DialogActions>
</Dialog>
</div>
);
}

Problems with React Routing

I have the following problem, i have a table with some data, and this table have an "edit" button. Now, i have an "Editar" component for this.
This is my App.js.
class App extends React.Component {
render(){
return (
<Container style = {{padding:"0",margin:"0"}} fluid>
<Row>
<Col>
<Router>
<Switch>
<Route path = "/dashboard">
<Index/>
</Route>
<Route path="/solicitud">
<Solicitud/>
</Route>
<Route path="/reserva">
<Reserva />
</Route>
<Redirect from = "/" to="/dashboard" />
</Switch>
</Router>
</Col>
</Row>
</Container>
)
}}
export default App;
Inside this "Index" component (in /dashboard path), i have the following table.
This is my "Index" component render function
render(){
return(
<Container style = {{padding:"0",margin:"0"}} fluid>
<Row>
<Col style = {{textAlign: "right"}} >
<Button href = "/solicitud" variant="primary">Agregar una Solicitud</Button>
</Col>
</Row>
<Row>
<Col md = {10}>
<h3 style = {{marginLeft:"40px"}}>Solicitudes</h3>
<List ItemList = {listsol} res = "0" />
</Col>
</Row>
<Row>
<Col md = {10}>
<h3 style = {{marginLeft:"40px"}}>Reservas</h3>
<List ItemList = {listres} res = "1"/>
</Col>
</Row>
</Container>
)}};
Now inside "Index" i create the tables with "List" component, where i create the "Editar" Button
List.js render function.
render(){
var res = this.props.res;
const lista = this.props.ItemList;
if (parseInt(res) === 0){
var todoItems = lista.map((obj) =>
<tr key={obj.id}>
<td>{obj.fecha}</td>
<td> {obj.hora}</td>
<td> {obj.equipo? "Sí":"No"}</td>
<td>{obj.equipamiento? "Sí":"No"}</td>
<td> {obj.sillon?"Sí":"No"}</td>
<td> {obj.paciente.rut} </td>
<td><Button variant="primary">Aprobar</Button> {''}
/*here i create the path */
<Router>
<Route path = "/dashboard/editar">
<Editar id = {obj.id} fecha = {obj.fecha} hora = {obj.hora} equipamiento = {obj.equipamiento}
sillon = {obj.sillon} rut = {obj.paciente.rut} />
</Route>
</Router>
/* here i create Editar Button */
<Button href = "/dashboard/editar" variant="primary">Editar</Button>{' '}
<Button onClick = {()=>{this.handleDel(obj.id)}} variant="danger">Rechazar Solicitud</Button></td>
</tr>);
}
else if (parseInt(res) === 1){
var todoItems = lista.map((obj) =>
<tr key={obj.id}>
<td>{obj.fecha}</td>
<td> {obj.hora}</td>
<td> {obj.equipo? "Sí":"No"}</td>
<td>{obj.equipamiento? "Sí":"No"}</td>
<td> {obj.sillon?"Sí":"No"}</td>
<td> {obj.paciente.rut} </td>
<td><Button onClick = {()=>{this.handleDel(obj.id)}} variant="primary">Terminar Procedimiento</Button> {''}
<Button variant="danger">Cancelar Reserva</Button></td>
</tr>);
}
return (
<Container>
<Table striped bordered hover>
<thead>
<tr>
<th>Fecha</th>
<th>Hora</th>
<th>Equipo</th>
<th>Equipamiento</th>
<th>Sillón</th>
<th>Rut</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{todoItems}
</tbody>
</Table>
</Container>
);
}
}
With this, when i press "Editar" Button this component renders inside the table, i suppose that its because i'm creating the path inside other component.
I tried creating the path in my App.js but i dont know how can i give the params to the component.
Sorry for so long question.

Convert Blob to image inside Cell in React

The value can be accessed like: myObj.value and it is blob:http://localhost:3000/304dbb9b-6465-4dc8-8b2c-5a25fae7e452
Cell component:
export default class Cell extends React.PureComponent {
render() {
const { label, value } = this.props;
return (
<td>
{label && (
<div>
<span className="cell-label">{label}</span>
<br />
</div>
)}<span>{value}</span>
)}
</td>
);
}
}
if I do it like in the following example, it shows the string value and I want to show the actual image. Is this possible?
<tr>
{myObj.map(item => (
<Cell
key={myObj.indexOf(item)}
label={item.label}
value={item.value} //I guess something here must be changed
/>
))}
</tr>
Try converting the blob to dataUrl:
URL.createObjectURL(blob)
Source: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
Then you can load it as an image:
<img src={URL.createObjectURL(dataUrl)} />
or in your code something like:
<tr>
{myObj.map(item => (
<Cell
key={myObj.indexOf(item)}
label={item.label}
value={URL.createObjectURL(item.value)}
/>
))}
</tr>
I'm hoping you can translate that into what you need. You can either transform the blob inside the component that renders <Cell /> or inside Cell. It sounds appropriate to put it inside Cell, so no other component has to care about the implementation details of the blob to dataUrl logic.
If you put it inside Cell, then you will need to call it like this:
// URL.createObjectURL(this.props.blob)
export default class ImageCell extends React.PureComponent {
render() {
const { label, blob } = this.props;
return (
<td>
{label && (
<div>
<span className="cell-label">{label}</span>
<br />
</div>
)}
<span>
<img src={URL.createObjectURL(blob)}>
</span>
)}
</td>
);
}
}
...
<tr>
{myObj.map(item => (
<ImageCell
key={myObj.indexOf(item)}
label={item.label}
blob={item.value}
/>
))}
</tr>

Categories