score:5

Accepted answer

I believe you are correct in your assessment. Let me try to rephrase for clarity:

@observer tracks which observables are used by render and automatically re-renders the component when one of these values changes.

We should note that @observable values used by render might be deeply nested within a given prop, per your example:

class Store{
  @observable data = {
    nestedData: {
      // changes to `deepData` would theoretically re-render any observer
      deepData: 'my_data' 
    }
  }
}

with observer, a change in all accessed observables in the last render will cause a re-render, even if the observable is nested deep in the data store

Bingo!

Although there's a quirk with observable, as you'll see in a moment...


On the other hand you have @inject which makes available to a component (via props) specific data structures defined by a Provider.

For example:

@inject('title')
class MyComponent extends React.Component {
    render() {
        return (<div>{this.props.title}</div>);
    }
}

const Container = () => (
    <Provider title="This value is passed as a prop using `inject`">
        <MyComponent />
    </Provider>
);

inject only re-renders when observables accessed in the injector function change.

Bingo!

inject will only spawn a re-render if the prop itself has recognized changes.


This is effectively the same issue with shouldComponentUpdate() and a deep-comparison of props -- though observer seems to be far more efficient than shouldComponentUpdate.

In my opinion, it's better to use only inject as it gives you more control, and can prevent unnecessary re-renders.

I wouldn't necessarily go that far... it all depends on how you have your code structured.

If I modify your original example as so:

class Store{
    @observable data = {
        nestedData: {}
    };

    constructor() {
        this.data.nestedData.deepData = 'my_data';
    }
}

...the addition of deepData won't actually get picked up as an observable change (i.e. re-render) because that property didn't exist when we originally tagged data as an observable value. So that's one problem.

A different approach could be to do something like this:

class Person {
    @observable name = 'John Doe';
}

class Store{
    @observable data = null;

    constructor() {
        this.data = new Person();
    }
}

This allows you to spread the observable values out across classes -- so you might still want to inject Store into a component (to access Store.data but ultimately any observable changes come from updating Person.


Related Query

More Query from same tag