Prevent all children re-rendering every call of setState - reactjs

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

Related

How to get React to wait for state to be set to render

userAccess is a function from another component which returns an array that I am trying to turn into a state variable and display it in render, however unless I implement an onclick=findSubs, the render does not show the correct state variable values. I thought putting it in componentDidMount would do something but it did not
import {userAccess} from '../../firebase';
class MySubscriptions extends Component{
constructor(props) {
super(props);
this.state = {subs:userAccess()};
this.findSubs = this.findSubs.bind(this);
}
componentDidMount(){
this.setState({subs:userAccess()});
}
findSubs(){
this.setState({subs:userAccess()});
}
render(){
return (
<div>
<Table>
<thead>
<tr>
<th>Subscription ID</th>
</tr>
</thead>
<tbody id="body">
{
this.state.subs.map((aSub) => (
<tr key ={aSub}>
<td key ={aSub}>{aSub.id}</td>
</tr>))
}
</tbody>
</Table>
</div>
)
}
}
export default MySubscriptions;
EDIT
This may not be the best way to do it, but it works
class MySubscriptions extends Component{
constructor(props){
super(props);
this.state ={subs:userAccess(), loaded:false};
}
async componentDidMount(){
await new Promise(resolve => { setTimeout(resolve, 500); });
this.setState({subs: await userAccess()}, () => {
this.setState({loaded:true});
return Promise.resolve();
});
}
render(){
return (
<div>
<Navbars/>
{this.state.loaded &&
<Table>
<thead>
<tr>
<th>Subscription ID</th>
</tr>
</thead>
<tbody id="body">
{
this.state.subs.map((aLine) => (
<tr>
<td>{aLine.id}</td>
</tr>
))
}
</tbody>
</Table>
}
</div>
)
}
}
export default MySubscriptions;
You could add a componentDidUpdate method.
In my opinion, the best practice is using the new Suspense component that was added with React 18, if you are using that version or higher.
You can read more about it and view code samples here.
If you have an earlier version, you can also use conditional rendering.

how to add pagination and fire row click event in table in react js

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.

Not able to render the new component on onClick()

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.

React collapse row onClick

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

Refreshing sorted table in React and state issue

I tried to sort an array in React, but I don't know how to refresh it. If I set data in a state like this: (data: this.props.data) pagination isn't working. Why is that?
render() {
let data = this.props.data;
return (
<div className='container'>
<table>
<thead>
<tr>
<th>iD</th>
<th>First name</th>
<th>Last name</th>
<th>Birth date</th>
<th onClick={() => {data.sort()}}>Company</th>
<th>Note</th>
</tr>
</thead>
<tbody>
{data.map((user) => {
return (
<tr key={user.id}>
<td className="number">{user.id}</td>
<td className="firstname">{user.firstName}</td>
<td className="lastname">{user.lastName}</td>
<td className="date">{user.dateOfBirth}</td>
<td className="company">{user.company}</td>
<td className="note">{user.note}</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
Check the code below
state = {
//use constructor or es7
data:this.props.data
}
_handleSort=()=>{
/**
* Define you short logic here.
*/
let sortedDate = this.state.data.dateOfBirth.sort()
this.setstate({
data:sortedDate
})
}
render() {
let {data} = this.state;
return (
<div className='container'>
<table>
<thead>
<tr>
<th>iD</th>
<th>First name</th>
<th>Last name</th>
<th>Birth date</th>
<th onClick={() => {this._handleSort}>Company</th>
<th>Note</th>
</tr>
</thead>
<tbody>
{data.map((user) => {
return (
<tr key={user.id}>
<td className="number">{user.id}</td>
<td className="firstname">{user.firstName}</td>
<td className="lastname">{user.lastName}</td>
<td className="date">{user.dateOfBirth}</td>
<td className="company">{user.company}</td>
<td className="note">{user.note}</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
The best way of fix this problem is sort array in parent component.
Instesd of data.sort() you will call this.props.sort() property and your table component will be updated with sorted data.
render() {
let data = this.props.data;
return (
<div className='container'>
<table>
<thead>
<tr>
<th>iD</th>
<th>First name</th>
<th>Last name</th>
<th>Birth date</th>
<th onClick={this.props.sort}>Company</th>
<th>Note</th>
</tr>
</thead>
<tbody>
{data.map((user) => {
return (
<tr key={user.id}>
<td className="number">{user.id}</td>
<td className="firstname">{user.firstName}</td>
<td className="lastname">{user.lastName}</td>
<td className="date">{user.dateOfBirth}</td>
<td className="company">{user.company}</td>
<td className="note">{user.note}</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
Parent :
class Parent extends Component {
constructor() {
super(props)
this.state = {
data: ....
}
}
sort = () => {
this.setState({
data: sortDataHere !!!!
})
}
render() {
return (
<Table
data={this.state.data}
sort={this.sort}
/>
)
}
}
Your component isn't properly controlled, since you're just using a reference obtained from props, which doesn't trigger render(). Components should instead be controlled on either state or props to trigger render() changes.
Here's an example to demonstrate that this pattern doesn't work:
class Example extends React.Component {
render () {
let text = this.props.data
return (
<div>
{text}
<button onClick={()=> (text='bye')}>
Click Me
</button>
</div>
)
}
}
ReactDOM.render(<Example data='hello' />, document.getElementById('container'))
<div id='container'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
What you are trying to do is control the component. You have two main options:
1) A stateful approach:
constructor (props) {
super(props)
this.state = { data: props.data }
}
render () {
return (
<th onClick={() => {sortTable.bind(this)}}>Company</th>
)
}
sortTable () {
let { data } = this.state
// sort algorithm,
this.setState({ data })
}
2) A Stateless Pattern using props and something like Redux
This is usually the preferred methodology as it keeps components purely as presentation without logic, which tend to be more reusable.
class Example extends React.Component {
render () {
const { data } = this.props
return (
<th onClick={() => this.props.sortData(data)}>Company</th>
)
}
}
const mapStateToProps = (state) => {
return {
data: state.data
}
}
const mapDispatchToProps = (dispatch) => {
return {
sortData: data => {
dispatch({ type: SORT, payload: data })
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Example)
Then in a state reducer:
export default function (state, action) {
switch (action.type) {
case SORT:
// transform the data
}
}
I tried to keep this above example minimal, and as such it will not work as is. It purely demonstrates how the connected component might look. To fully use redux you need to setup your application with a <Provider> and a store.

Resources