React collapse row onClick - reactjs

I have data that I need to show in table, for all rows I would like to use collapse function. Now I have working code that is collapsing all rows on click, but in what way I can collapse only one already clicked?
This is my code:
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
datas: [
{name: "or1", status: 11},
{name: "or3", status: 2},
{name: "or2", status: 22},
],
expanded: [],
isOpen: true,
};
}
toogleContent(openIs) {
this.setState({
isOpen: !openIs
})
}
render() {
return (
<Table responsive>
<thead className="text-primary">
<tr>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{this.state.datas.map((data, i) => (
<tr id={i} onClick={this.toogleContent.bind(this, this.state.isOpen)}>
<td>{data.name}</td>
<td><Collapse isOpen={this.state.isOpen}>
{data.status}</Collapse>
</td>
</tr>
))}
</tbody>
</Table>
)
}
}

I used their indexes to find out if they are opened or not, but you can use their names too, or add at data object a key isOpened =>
state={
isOpenIndexes = []
}
toogleContent(index) {
// we check if already opened
this.state.isOpenIndexes.find(openedIndex => openedIndex ===index ) ?
this.setState({
isOpenIndexes: this.state.isOpenIndexes.filter(openedIndex => openedIndex!==index)
})
:
this.setState({
isOpenIndexes: isOpenIndexes.concat([index])
})
}
{datas.map((data, i) => (
<tr id={i} onClick={this.toogleContent.bind(this, i)} isOpened={this.state.isOpenIndexes.find(openedIndex => openedIndex ===i )}>
<td>{data.name}</td>
<td>{data.status}</td>
</tr>
))}

Related

How can I only manipulate only 1 item in the array map independently

I have created a table like this, and I created all of these buttons in the array map function
Whenever I click on the Edit button in every button, it'll display all of it at the same time
How can I press Edit, for example, Edit button on RoleID 1, it'll only display the Edit table there for me to edit, not all of it, I don't know how to separate it since it's stuck in the map array I've created.
Here is my code, I have shorten it for easier to read:
class RoleManagement extends Component {
constructor(props) {
super(props);
this.state = {
roleList: [],
showHideEdit: false,
};
this.toggleEdit = this.toggleEdit.bind(this);
}
async componentDidMount() {
await axios.get("http://localhost:3000/admin/get-role-list").then((res) => {
this.setState({
roleList: res.data,
});
});
}
/---This is the toggle to display edit
toggleEdit(name) {
switch (name) {
case "showHideEdit":
this.setState({ showHideEdit: !this.state.showHideEdit });
break;
default:
null;
}
}
render() {
const { showHideEdit } = this.state;
return (
<table>
<thead>
<tr>
<th>Role ID</th>
<th>Role Name</th>
<th>Action</th>
</tr>
</thead>
{this.state.roleList.map((element, i) => {
return (
<>
<tbody key={i}>
<tr>
<td>{element.role_ID}</td>
<td>{element.rolename}</td>
<td className="admin-button">
/---- The edit button is here
<button
className="admin-config-button"
onClick={() => this.toggleEdit("showHideEdit")}
>
Edit
</button>
{showHideEdit && <RoleEdit id={element.key} />}
</td>
</tr>
</tbody>
</>
);
})}
);
}
}
Because you are using one boolean to check for all RoleEdit. Show when showHideEdit is true, all RoleEdit will show.
To fix, you can update showHideEdit is index of item:
onClick={() => this.toggleEdit(i)}
toggleEdit(i){
if(this.state.showHideEdit === i) {
this.setState({ showHideEdit: null});
} else {
this.setState({ showHideEdit: i});
}
}
{showHideEdit === i && <RoleEdit id={element.key} />}

Child component should render similar child component as sibling in parent component

I am new to react so bear any nontechnical words.
I have parent component that displays the table headers, now next is child component which has tables' td along with one td is add button when the user clicks on add button. The similar child component should be added as a sibling to previous child component and this process should go on.
Child Component:
class ChildComp extends React.Component{
state = {
Avalue: {value: ''},
Bvalue: {value: ''},
Cvalue: {value: ''},
Dvalue: {value: ''},
Evalue: {value: ''},
Fvalue: {value: ''},
Gvalue: {value: ''},
}
AddanotherSimilarChildcomp=(e)=>{
e.preventDefault();
const historyData = {
A: this.state.A.value,
B:this.state.B.value,
C: this.state.C.value,
D: this.state.D.value,
E: this.state.E.value,
F: this.state.F.value,
G: this.state.G.value,
}
console.log(historyData);
//and should render another similar ChildComp component below the one in which the current ChildComp is present
}
handleChange=(e)=>{
e.preventDefault();
const target = e.target;
const inputName = target.name;
const inputValue = target.value;
this.setState({
[inputName] : {
value: inputValue,
}
});
}
render(){
return(
<tbody id="first-table-row">
<tr>
<td data-th="A"><input type="text" minmax="40" name="A" value={this.state.a.value} onChange={this.handleChange} /></td>
<td data-th="B"><input type="date" minmax="40" name="B" value={this.state.B.value} onChange={this.handleChange} /></td>
<td data-th="C"><input type="text" minmax="225" name="C" value={this.state.C.value} onChange={this.handleChange} /></td>
<td data-th="D"><input type="text" minmax="40" name="D"value={this.state.D.value} onChange={this.handleChange} /></td>
<td data-th="E"><input type="text" minmax="40" name="E" value={this.state.E.value} onChange={this.handleChange} /></td>
<td data-th="F"><input type="text" minmax="40" name="F" value={this.state.F.value} onChange={this.handleChange} /></td>
<td data-th="G">
<div id="samerow">
<span>{this.props.somedata}</span>
<input type="text" minmax="40" name="G"value={this.state.G.value} onChange={this.handleChange} />
</div>
</td>
<td className="save" ><button id="save-btn" onClick={this.AddanotherSimilarChildcomp} type='button'>Add</button></td>
</tr>
</tbody>
)
}
}
Parent Component:
class ParentComponent extends React.PureComponent{
render(){
return(
<div className='table-center' id='table1'>
<table className="rwd-table" id="tblBlah" >
<tbody>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
<th>E</th>
<th>F</th>
<th>G</th>
<th> </th>
</tr>
</tbody>
<ChildComp/>
</table>
</div>
)
}
}
It sounds like you want to clone rows after the button is clicked.. Let me know if this is what you are looking for..
Hope this helps!
class ParentComponent extends React.Component {
render() {
return (
<div>
<table>
<tbody>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
<th>E</th>
<th>F</th>
<th>G</th>
<th> </th>
</tr>
</tbody>
<ChildComp
rows={[
[1, 2, 3, 4, 5, 6, 7],
[8, 9, 10, 11, 12, 13, 14],
[14, 15, 16, 17, 18, 19, 20]
]}
/>
</table>
</div>
);
}
}
class ChildComp extends React.Component {
state = {
tableRows: []
};
componentDidMount() {
this.setState({ tableRows: this.props.rows });
}
addNewRow = (rowToClone, index) => event => {
let newRows = [...this.state.tableRows];
newRows.splice(index, 0, rowToClone.map(i => `${i}` + `${i[0] || i}`));
this.setState({ tableRows: newRows });
};
render() {
return (
<tbody>
{this.state.tableRows.map((row, index) => {
return (
<tr>
{row.map(i => <td>{i}</td>)}
<td>
<button onClick={this.addNewRow(row, index + 1)}>
Clone Row
</button>
</td>
</tr>
);
})}
</tbody>
);
}
}
ReactDOM.render(<ParentComponent />, document.body);
table, th, tr, td {
border: 1px solid black;
}
table {
border-collapse: collapse;
}
td {
width: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
I think you are over-complicating the logic for your component-tree. This is how I interpreted your question.
You have a Parent component that renders a table with headers.
Each Child component is a table row.
You have a button that should add more rows (Child components).
The table cells belonging to each row should be editable (hence
inputs).
See working sandbox: https://codesandbox.io/s/inspiring-wave-bpv7f
It makes the most sense to have the Parent component manage the data-set. So all logic that involves updating or adding something from the data-set should belong to the Parent, not the Child components.
Consider an example like this;
Parent.js
class App extends React.Component {
state = {
headers: ["name", "age", "job", "salary"],
data: [
[
{ name: "Bobby Hill", editting: false },
{ age: 13, editting: false },
{ job: "student", editting: false },
{ salary: 0, editting: false }
]
]
};
createHeaders = () => {
const { headers } = this.state;
return headers.map(item => <th className="table-header">{item}</th>);
};
createBody = () => {
const { data } = this.state;
return data.map((row, index) => (
<Child data={row} rowIndex={index} toggleEdit={this.toggleEdit} />
));
};
addRow = () => {
const { data } = this.state;
const rowTemplate = [
{ name: "", editting: true },
{ age: "", editting: true },
{ job: "", editting: true },
{ salary: "", editting: true }
];
this.setState({
data: [...data, rowTemplate]
});
};
toggleEdit = (rowIndex, cellIndex) => {
const dataCopy = [...this.state.data];
const itemToUpdate = dataCopy[rowIndex][cellIndex];
itemToUpdate.editting = !itemToUpdate.editting;
this.setState({
data: dataCopy
});
};
render() {
return (
<div className="table-center" id="table1">
<table className="rwd-table" id="tblBlah">
<tbody>
<tr>{this.createHeaders()}</tr>
{this.createBody()}
</tbody>
</table>
<button onClick={this.addRow}>Add Row</button>
</div>
);
}
}
In the Parent component we have:
A data-set in our state which is an array of arrays. Each array
pertains to table-row.
When we iterate over the data-set, we pass in an array to each Child
component, which will render the row.
The addRow function will add a new array to our data-set,
containing objects corresponding to each table-header/property.
The toggleEdit function helps us swap between edit modes for each
cell.
Child.js
import React from "react";
const Child = ({ data, rowIndex, toggleEdit }) => {
return (
<tr>
{data.map((cell, cellIndex) => (
<td
className="table-cell"
onDoubleClick={() => toggleEdit(rowIndex, cellIndex)}
>
{cell.editting ? (
<input value={Object.values(cell)[0]} />
) : (
<span>{Object.values(cell)[0]}</span>
)}
</td>
))}
</tr>
);
};
export default Child;
Now in the Child component:
It consumes the data (array) that was passed to it was a prop and
uses it to render the row.
For each item (object) in the array, it creates a table-cell to
display the value.
Double-clicking the cell will toggle the edit property of the item belonging to the parent-state.
If editting is true, than an input is displayed, if not just a
normal table-cell.

How to handle a onClick method in a component?

I believe I have my props setup correctly, but I keep getting "Cannot read property 'props' of undefined"
Child component:
const SearchResults = props => (
<div>
<div className="row" ><h4 style={{margin:"auto", marginBottom:"15px"}}>Search Results:</h4></div>
<table className="table table-striped">
<thead>
<tr>
{props.labels.map(label => ( <th key={label.Id}>{label.DisplayName}</th>))}
</tr>
</thead>
<tbody>
{ props.contracts.map((contract, i) => <tr key={i} data-id={contract.Id} onClick={() => this.props.handleContract(contract.Fields.filter(field => field.DataField==="IDXT001").map(field => field.DataValue))} className="clickable-row">{contract.Fields.map( docs => <td key={docs.Id}><span id={docs.DataField}>{docs.DataValue}</span></td>)}</tr> )}
</tbody>
</table>
</div>
)
Parent:
class SearchPage extends React.Component {
constructor(props) {
super(props);
this.state = {
labels: [],
contracts: [],
formValues:"",
pdfs:[],
id:"",
};
}
<SearchResults
labels={this.state.labels}
contracts={this.state.contracts}
pdfs={this.state.pdfs}
handleContract={this.onClick}
/>
You need to change this.props.handleContract to props.handleContract. this doesn't exist in your functional component.
const SearchResults = props => (
<div>
<div className="row" ><h4 style={{ margin: "auto", marginBottom: "15px" }}>Search Results:</h4></div>
<table className="table table-striped">
<thead>
<tr>
{props.labels.map(label => (<th key={label.Id}>{label.DisplayName}</th>))}
</tr>
</thead>
<tbody>
{props.contracts.map((contract, i) => <tr key={i} data-id={contract.Id} onClick={() => props.handleContract(contract.Fields.filter(field => field.DataField === "IDXT001").map(field => field.DataValue))} className="clickable-row">{contract.Fields.map(docs => <td key={docs.Id}><span id={docs.DataField}>{docs.DataValue}</span></td>)}</tr>)}
</tbody>
</table>
</div>
);
On your parent, make sure you have bound the onClick function as mentioned in the comments.
class SearchPage extends React.Component {
constructor(props) {
super(props);
this.state = {
labels: [],
contracts: [],
formValues: "",
pdfs: [],
id: ""
};
}
onClick = () => {
// Bind the function like this.
}
render() {
return (
<SearchResults
labels={this.state.labels}
contracts={this.state.contracts}
pdfs={this.state.pdfs}
handleContract={this.onClick}
/>
);
}
}

How to pending button show approved on click event by reactjs

When I click the "pending" button, the status does not change with this code, and shows the message
TypeError: this.state.data.map is not a function
Please help me!
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [
{
id: 1,
name: "farid",
age: 15,
status: "pending"
},
{
id: 2,
name: "kamal",
age: 15,
status: "pending"
}
],
}
}
movementappTDClick = (id) => () => {
const index = this.state.data.findIndex((data) => {
return data.id === id
})
let data = Object.assign({}, this.state.data[index]);
console.log('data is', data);
data.status = "approved"
console.log("sec data is", data);
this.setState({ data });
};
render() {
return (
<div className="App">
<Card>
<CardBody>
<Table>
<thead>
<tr>
<th>ID</th>
<th>name</th>
<th>age</th>
</tr>
</thead>
<tbody>
{this.state.data && this.state.data.map(movement => (
<tr key={movement.id}>
<th>{movement.id}</th>
<td>{movement.name}</td>
<td>{movement.age} </td>
<td>
<button
onClick={this.movementappTDClick(movement.id)}
>
{movement.status}
</button>
</td>
</tr>
))}
</tbody>
</Table>
</CardBody>
</Card>
</div>
);
}
}
try to modify your movementappTDClick function.
movementappTDClick = index => {
const data = [...this.state.data];
const row = data[index];
row.status = "approved";
data.splice(index, 1, row);
this.setState({ data });
};
render() {
return (
<div className="App">
<Card>
<CardBody>
<Table>
<thead>
<tr>
<th>ID</th>
<th>name</th>
<th>age</th>
</tr>
</thead>
<tbody>
{this.state.data &&
this.state.data.map((movement, index) => (
<tr key={movement.id}>
<th>{movement.id}</th>
<td>{movement.name}</td>
<td>{movement.age} </td>
<td>
<button onClick={() => this.movementappTDClick(index)}>
{movement.status}
</button>
</td>
</tr>
))}
</tbody>
</Table>
</CardBody>
</Card>
</div>
);
}
}
I modified your code. Please check this.
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [
{
id: 1,
name: "farid",
age: 15,
status: "pending"
},
{
id: 2,
name: "kamal",
age: 15,
status: "pending"
}
],
}
}
movementappTDClick = (index) => () => {
const { data } = this.state;
data[index] = {
...data[index],
status: 'approved'
}
this.setState({ data }); };
render() {
const { data } = this.state;
return (
<div className="App">
<Card>
<CardBody>
<Table>
<thead>
<tr>
<th>ID</th>
<th>name</th>
<th>age</th>
</tr>
</thead>
<tbody>
{data && _.map((data || []), (movement, index) => (
<tr key={movement.id}>
<th>{movement.id}</th>
<td>{movement.name}</td>
<td>{movement.age} </td>
<td>
<button
onClick={this.movementappTDClick(index)}
>
{movement.status}
</button>
</td>
</tr>
))}
</tbody>
</Table>
</CardBody>
</Card>
</div>
);
}
}

Prevent all children re-rendering every call of setState

When rendering a long list of elements in a table for example, then any call to setState regardless of whether it changes the data the table uses for enumeration results in a re-render of every child object.
Every check/uncheck will re-render every element. This causes major slowdown with 2000 elements that are more complex. I will use virtualization, but want to make sure this is as performant as possible before doing so.
class App extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
const rows = Array.from({ length: 200 }, (v, k) => k + 1).map(i => ({
id: i,
field1: "hello",
field2: "cruel",
field3: "world"
}));
this.state = {
selectedIds: [],
rows
};
}
onChange(e) {
const name = e.target.name;
const checked = e.target.checked;
const selectedIds = [...this.state.selectedIds];
if (!checked) {
const index = selectedIds.findIndex(x => x === name);
selectedIds.splice(index, 1);
} else {
selectedIds.push(name);
}
this.setState(state => ({ selectedIds }));
}
render() {
const { rows, selectedIds } = this.state;
return (
<div className="App">
<h5>{selectedIds.length} Rows Selected</h5>
<table>
<thead>
<tr>
<td>Select</td>
</tr>
<tr>
<td>Field 1</td>
</tr>
<tr>
<td>Field 2</td>
</tr>
<tr>
<td>Field 3</td>
</tr>
</thead>
<tbody>
{rows.map(row => {
console.log(row);
return (
<tr key={row.id}>
<td>
<input
type="checkbox"
onChange={this.onChange}
name={row.id}
/>
</td>
<td>
<div>{row.field1}</div>
</td>
<td>
<div>{row.field2}</div>
</td>
<td>
<div>{row.field3}</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
}
I see from other answers that it is the expected behaviour to re-run the render function
ReactJS - Does render get called any time "setState" is called?
But if so, when the number of elements is increased to say 4000 in that link, why is there so much slowdown when clicking? Significant slowdown is noticed when only using 200 items when using slightly more complex custom React Components.
To solve this issue you'll want to create an additional component for the row item. This way it will have a separate render function and not rerender itself because the row component hasn't changed, only the table component.
{rows.map(row => {
return (
<RowItem row={row} onChange={this.onChange}>
);
}}
and your new component will look something like this:
class RowItem extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<tr key={this.props.row.id}>
<td>
<input
type="checkbox"
onChange={this.props.onChange}
name={this.props.row.id}
/>
</td>
<td>
<div>{this.props.row.field1}</div>
</td>
<td>
<div>{this.props.row.field2}</div>
</td>
<td>
<div>{this.props.row.field3}</div>
</td>
</tr>
);
}
}

Resources