Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MobX - Why should I use `observer` when I could use `inject` when injecting data into a React component

MobX documentation suggests I should use observer on all my components. However by using inject I get more fine grained control over what data causes a re-rendering of my components.

My understanding is I that 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, while inject only re-renders when observables accessed in the injector function change.

For example:

class Store{
  @observable data = {
    nestedData: {
      deepData: 'my_data'
    }
  }
}

const store = new Store();

... Assume the store is injected using <Provider /> component

// This will cause re-render when the data object changes
// for example: store.data = { new_data: 'new_data' }
@inject(stores => {
  return { data: stores.dataStore.data }; 
})
class MyComponent extends Component {  }

// This will re-render on change of the data object, but also
// on change of nestedData and deepData properties
@inject(stores => {
  return { data: stores.dataStore.data }; 
})
@observer
class MyComponent extends Component {  }

Could someone confirm my understanding of this?

In my opinion, it's better to use only inject as it gives you more control, and can prevent unnecessary re-renders. If the data is deeply nested you could create a computed property that gets and prepares the data from the deep structure and then inject that property in the component.

Are there other benefits/drawbacks when using one over the other

like image 641
Mateo Hrastnik Avatar asked Jan 25 '18 06:01

Mateo Hrastnik


People also ask

What is inject and observer in MobX?

My understanding is I that 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, while inject only re-renders when observables accessed in the injector function change.

What does MobX observer do?

The observer HoC automatically subscribes React components to any observables that are used during rendering. As a result, components will automatically re-render when relevant observables change. It also makes sure that components don't re-render when there are no relevant changes.

What is observable in MobX?

observable defines a trackable field that stores the state. action marks a method as action that will modify the state. computed marks a getter that will derive new facts from the state and cache its output.

Does MobX work with functional components?

In this tutorial, we will learn how to use MobX with React Functional Components. MobX being a very useful state management library reduces the code that needs to be written for global state management. Mobx is easy to use and quick to learn.


1 Answers

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.

like image 172
arthurakay Avatar answered Sep 27 '22 19:09

arthurakay