Jesse Hallam
Jesse Hallam

Reputation: 6964

Why does this React/Mobx observer component not rerender when its observable props change?

I have a React component which renders a table of data from its props. The data is wrapped in MobX's observable(). From time to time, an asynchronous operation (AJAX call) adds a new row to the data source, which should cause a new table row to render. This is not the case.

A minimal example of the component in question is below:

@observer
class MyDashboard extends React.Component {
    render() {
        return (
            <Table
                columns={this.props.columns}
                dataSource={this.props.model.contentTypes}
            />
        );
    }

    @action.bound
    private onThingHappened = async () => {
        const thing = await asyncThing();
        runInAction('onThingHappened', () => {
            this.props.model.contentTypes.push(thing);
        });
    }
}

The prop model is an observable object. model.contentTypes is an array of objects. The table renders correctly on the first draw; it's subsequent re-renders which are problematic.

The bound action onThingHappened works as expected if the await is removed. To put it differently - model changes which occur before the first await trigger re-renders as expected.

The runInAction does not seem to noticably change anything.

However, if I draw model.contentTypes in the component itself - rather than simply passing it down to a child component (<Table> in this case), the entire thing works as expected. For example:

    return (
        <React.Fragment>
            <div style={{display: 'none'}}>{this.props.model.contentTypes.length}</div>
            <Table
                columns={this.props.columns}
                dataSource={this.props.model.contentTypes}
            />
        </React.Fragment>
    )

In this render function, dereferencing the model.contentTypes array seems to be enough to trigger the re-render in onThingHappened, even after an await.

Upvotes: 1

Views: 684

Answers (1)

xadm
xadm

Reputation: 8418

I don't use mobx but after some research:

  • wrapping <Table/> in observer() should work ... if <Table/> render uses the value directly, it can fail if passed to (unwrapped) subcomponent;

'Mobx traces access' then probably it's enough to force mobx to react by 'unnecessary' access.

Try sth like:

render() {
  const amount = this.props.model.contentTypes.length;
  if( !amount ) return null; // to avoid 'amount unused, no unused ...'

  return (
    <Table
      columns={this.props.columns}
      dataSource={this.props.model.contentTypes}
    />
  );
}

Upvotes: 1

Related Questions