I have a react component that receives props from the redux store every second. The new state has an array that's different than the last array. To be specific, every second an element is added to the array. For example: in one state the array is:
[1, 2, 3, 4, 5, 6]
the next state
[1, 2, 3, 4, 5, 6, 7]
My reducer:
return {
...state,
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
}
Now, in my react component I am subscribing to this state and for every change, the list is re-rendered.
import React, { Component } from 'react';
import MyRow from './MyRow';
class MyList extends Component {
render() {
return (
<div>
{this.props.myList.map((list, index) => (
<MyRow key={list.id} data={list}/>
))}
</div>
);
}
}
function select({ myList }) {
return { myList };
}
export default connect(select)(MyList);
In MyRow.js
import { PureComponent } from 'react';
class MyRow extends PureComponent {
render() {
const data = this.props.data;
return (
<div>
{data.id} - {data.name}
</div>
);
}
}
export default MyRow;
Now, my problem is: It's costly for me to re-render every element that has been already rendered. The MyRow heavily uses styled components and other expensive operations. This is causing react to re-render the whole list every second when the state is updated. This gets worst if updates come in less than 1 seconds, like 4 updates per second. The react app simply crashes in this case.
Is there any way to only add the newly added item to the list and not re-render the whole list?
Thanks
You're using PureComponent, that do shallow comparison, then your component MyRow
should not be rerendered on each new item being added (Please follow my code example below).
Is there any way to only add the newly added item to the list and not re-render the whole list?
According to your question - Yes, using PureComponent
should render only 1 time the new item:
Here's what the React's docs says:
If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
PureComponent
:You can check out the code sample, that I did for you.
You will see that the Item
component is always rendered only 1 time, because we use React.PureComponent
. To prove my statement, each time the Item
is rendered, I added current time of rendering. From the example you will see that the Item
Rendered at:
time is always the same, because it's rendered only 1 time.
const itemsReducer = (state = [], action) => {
if (action.type === 'ADD_ITEM') return [ ...state, action.payload]
return state
}
const addItem = item => ({
type: 'ADD_ITEM',
payload: item
})
class Item extends React.PureComponent {
render () {
// As you can see here, the `Item` is always rendered only 1 time,
// because we use `React.PureComponent`.
// You can check that the `Item` `Rendered at:` time is always the same.
// If we do it with `React.Component`,
// then the `Item` will be rerendered on each List update.
return <div>{ this.props.name }, Rendered at: { Date.now() }</div>
}
}
class List extends React.Component {
constructor (props) {
super(props)
this.state = { intervalId: null }
this.addItem = this.addItem.bind(this)
}
componentDidMount () {
// Add new item on each 1 second,
// and keep its `id`, in order to clear the interval later
const intervalId = setInterval(this.addItem, 1000)
this.setState({ intervalId })
}
componentWillUnmount () {
// Use intervalId from the state to clear the interval
clearInterval(this.state.intervalId)
}
addItem () {
const id = Date.now()
this.props.addItem({ id, name: `Item - ${id}` })
}
renderItems () {
return this.props.items.map(item => <Item key={item.id} {...item} />)
}
render () {
return <div>{this.renderItems()}</div>
}
}
const mapDispatchToProps = { addItem }
const mapStateToProps = state => ({ items: state })
const ListContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(List)
const Store = Redux.createStore(itemsReducer)
const Provider = ReactRedux.Provider
ReactDOM.render(
<Provider store={Store}>
<ListContainer />
</Provider>,
document.getElementById('container')
)
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
MyRow
rerending, please find out what's the reason of rerending, because it should not happen, because of PureComponent
usage.
myList: [ ...state.myList, payload ]
key
to your item component <MyRow key={list.id} data={list} />
. If the key
or data
props are changed, then the component will be rerendered. Here are some other libraries, these stand for efficient rendering of lists. I'm sure they will give us some alternatives or insights:
PureComponent will shallowly compare the props and state. So my guess here is that the items are somehow new objects than the previous passed props, thus the rerendering.
I would advice, in general, to only pass primitive values in pure components :
class MyList extends Component {
render() {
return (
<div>
{this.props.myList.map((item, index) => (
<MyRow key={item.id} id={item.id} name={data.name} />
//or it's alternative
<MyRow key={item.id} {...item} />
))}
</div>
);
}
}
//...
class MyRow extends PureComponent {
render() {
const {id, name} = this.props;
return (
<div>
{id} - {name}
</div>
);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With