12

I have table of customers and the selected customer is stored in the ViewState. The problem is that all rows re-render when the selection changes which is quite slow. Ideally only the selected row and the row previously selected would re-render, but I did not find out how to achieve this. My structure is identical to the example in the MobX contact list example:

{this.filteredCustomers.map(customer => {
   return (
      <CustomerRow
         key={customer.id}
         customer={customer}                    
         viewState={this.props.store.view}
      />
   )                
})}

and

const CustomerRow = observer((props: CustomerRowProps) => {
   const isSelected = props.viewState.isCustomerSelected(props.customer)
   const rowClass = isSelected ? 'active' : ''

   return (
     <tr className={rowClass}>
       <td>{props.customer.lastName}</td>
       <td>{props.customer.firstName}</td>
     </tr>
   )
})

All rows depend on the value of ViewState.selectedCustomer through the isCustomerSelected method.

Is there another way to structure this that avoids re-rendering all rows?

jpfollenius
  • 15,826
  • 9
  • 83
  • 148
  • Can you upload `CustomerRow` render as well ? – An Nguyen Aug 26 '17 at 11:29
  • It's hard to say without seeing both components in their entirety, but I recreated an example, and [**it works for me**](http://jsbin.com/qapilekina/edit?js,output). – Tholle Aug 26 '17 at 11:50
  • @Tholle: the "trick" in your case is that you put the selection information into each row with the `active` flag, so that the row only depends on the object and not on the view state. Is that a general pattern? – jpfollenius Aug 26 '17 at 12:29
  • Yeah, it's a common pattern. It is e.g. used in the [**introduction to MobX**](https://youtu.be/K8dr8BMU7-8?t=7m11s) by Weststrate, the creator of MobX. But it was not be the best example by me, since you might not want to add extra fields to the data just for the view. – Tholle Aug 26 '17 at 12:37
  • have you tried `PureComponent` instead of regular `Component`? https://facebook.github.io/react/docs/react-api.html#react.purecomponent – Sagiv b.g Aug 29 '17 at 20:44

2 Answers2

4

You can use shouldComponentUpdate to deside wheather component updates or not.

bennygenel
  • 20,219
  • 5
  • 51
  • 71
  • 1
    No, that's what I am using mobx for. Or are there cases where one has to combine mobx and shouldComponentUpdate? – jpfollenius Aug 30 '17 at 05:28
  • If using one of them is not working for you I think you need to combine them. – bennygenel Aug 30 '17 at 06:24
  • It is worth noting that marking a component with @observer will remove the need to implement shouldComponentUpdate in that component, in fact it will give you a warning "Use `shouldComponentUpdate` in an `observer` based component breaks the behavior of `observer` and might lead to unexpected results. Manually implementing `sCU` should not be needed when using mobx-react" – Saif Asad Mar 13 '19 at 03:38
3

The reason why all the rows are re-rendered is because props.viewState.isCustomerSelected(props.customer) has to be re-evaluated for each observer component when the used observable change.

One way to get around this is to use a map so that every entry will have a potential checked field of their own, so that only the selected and deselected components have to re-render.

Example (JSBin)

class AppState {
  @observable todos = [
    {
      id: '1',
      title: 'Do something'
    }, 
    {
      id: '2',
      title: 'Do something else'
    },
    {
      id: '3',
      title: 'Do a third thing'
    }
  ]

}

var appState = new AppState();

@observer
class Todos extends React.Component {
  checked = observable.map({});

  changeTodo = (todo) => {
    this.checked.clear();
    this.checked.set(todo.id, true);
  };

  render() {
    return <div>
      <ul>
        { this.props.appState.todos.map((todo) =>
          <Todo 
            todo={todo} 
            key={todo.id}
            checked={this.checked}
            onChange={() => this.changeTodo(todo)} />
        ) }
      </ul>
      <DevTools />
    </div>;
  }
}

@observer
class Todo extends React.Component {
  render() {
    const { todo, checked, onChange } = this.props;
    const isChecked = checked.get(todo.id);
    return <li>
      <input 
        type="checkbox"
        checked={isChecked} 
        onChange={onChange} />
      {todo.title}
    </li>;
  }
}
Tholle
  • 83,208
  • 13
  • 152
  • 148