Matteo Mazzarolo Matteo Mazzarolo - 1 year ago 102
React JSX Question

Re-rendering a single row of a list without re-rendering the entire list

we're trying to implement a contact list that works just like the new Material Design Google Contacts (you must enable the material design skin to see it) using material-ui.

Specifically we're trying to show a checkbox instead of the avatar on row hover.

We'd like to catch and re-render only the interested row (when hovered) and show the avatar/checkbox accordingly... this seems an easy task but we're not able to isolate the render to the hovered row (instead of re-rendering the entire list)
Do you have any suggestion on how to do something like this?

Our temporary solution uses a container component that handles the table:
When a row is hovered we capture it from

of the
component and save it in the container state. This triggers a re-render of the entire list with really poor perfomance.

You can see a video of the issue here.

Here is a code sample:

import React from 'react'
import Avatar from 'material-ui/lib/avatar'
import Checkbox from 'material-ui/lib/checkbox'
import Table from 'material-ui/lib/table/table'
import TableHeaderColumn from 'material-ui/lib/table/table-header-column'
import TableRow from 'material-ui/lib/table/table-row'
import TableHeader from 'material-ui/lib/table/table-header'
import TableRowColumn from 'material-ui/lib/table/table-row-column'
import TableBody from 'material-ui/lib/table/table-body'
import R from 'ramda'

export default class ContactsList extends React.Component {

constructor (props) {
this.state = { hoveredRow: 0 }
this.contacts = require('json!../../public/contacts.json').map((e) => e.user) // Our contact list array

_handleRowHover = (hoveredRow) => this.setState({ hoveredRow })

_renderTableRow = ({ hovered, username, email, picture }) => {
const checkBox = <Checkbox style={{ marginLeft: 8 }} />
const avatar = <Avatar src={picture} />
return (
<TableRow key={username}>
<TableRowColumn style={{ width: 24 }}>
{hovered ? checkBox : avatar}

render = () =>
<TableHeader displaySelectAll enableSelectAll>
<TableBody displayRowCheckbox={false} showRowHover>
{, index) => this._renderTableRow({
hovered: index === this.state.hoveredRow, }))

Thank you in advance.

Answer Source

You could wrap your rows into a new component implementing shouldComponentUpdate like so :

class ContactRow extends Component {
    shouldComponentUpdate(nextProps) {
        return this.props.hovered !== nextProps.hovered || ...; // check all props here
    render() {
        const { username, email, ...otherProps } = this.props;
        return (
            <TableRow { ...otherProps } >
                <TableRowColumn style={{ width: 24 }}>
                    {this.props.hovered ? checkBox : avatar}

Then you can use it in your ContactList component like so :, index) => <ContactRow key={contact.username} {} hovered={index === this.state.hoveredRow} />)

If you don't want to manually implement shouldComponentUpdate, you can use React's PureRenderMixin or check a lib like recompose which provides useful helpers like pure to do so.


As pointed out by @Denis, the approach above doesn't play well with some features of the Table component. Specifically, TableBody does some manipulation on its children's children. A better approach would be to define your ContactRow component like so:

class ContactRow extends Component
    shouldComponentUpdate(nextProps) {
        // do your custom checks here
        return true;
    render() {
        const { username, email, ...otherProps } = this.props;
        return <TableRow { ...otherProps } />;

and then to use it like this

<ContactRow { ...myProps }>

But I guess having TableRow re-render only when necessary is a feature everyone would benefit from, so maybe a PR would be in order :)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download