Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make MobX update the component when observable is not used in the render method?

After switching from Redux to MobX for React I'm starting to extremely like MobX. It's pretty awesome.

MobX has a certain behavior where it won't update component if the provided store observable is not used in render. I think generally that's a pretty great behavior that makes components render only when something actually changed.

But... I do encountered couple of cases where I do not want or need to use MobX observable inside render method and in such cases my this.props.store with MobX store won't get updated.

And in such cases, as a workaround, I just reference the observable in the render method, but I don't think that's a very clean approach, and I'm wondering if there's a cleaner way to do that?

This component code should explain even more what I'm exactly asking about. It's a component that changes body overflow style based on a MobX observable that I have in my store.

/*------------------------------------*\
  Imports
\*------------------------------------*/
import React from 'react';
import connectStore from 'script/connect-store';
import addClass from 'dom-helpers/class/addClass';
import removeClass from 'dom-helpers/class/removeClass';


/*------------------------------------*\
  Component
\*------------------------------------*/
class Component extends React.Component {

  constructor(props) {
    super(props);
    this.constructor.displayName = 'BodyClassSync';
  }

  componentDidMount() {
    this.checkAndUpdateMuiClass();
  }

  componentDidUpdate() {
    this.checkAndUpdateMuiClass();
  }

  checkAndUpdateMuiClass() {

    // This place is the only place I need latest MobX store... 
    if (this.props.store.muiOverlay) {
      addClass(window.document.body, 'mod-mui-overlay');
    }

    else {
      removeClass(window.document.body, 'mod-mui-overlay');
    }

  }


  render() {

    // This is my workaround so the componentDidUpdate will actually fire
    // (with latest and updated store)
    // when the 'muiOverlay' observable changes in the MobX store
    // Is there any cleaner/better way to do this?
    this.props.store.muiOverlay;

    // This component doesn't and shouldn't render anything
    return null;

  }



}



/*------------------------------------*\
  Export
\*------------------------------------*/
const ComponentWithStore = connectStore(Component);
export default ComponentWithStore;

(Note: I don't use @decorators, I connect store using ES5 syntax in the connectStore function. And it would be awesome if solution to this would be also in ES5.)

like image 763
Dominik Serafin Avatar asked Jul 11 '18 11:07

Dominik Serafin


2 Answers

You could use an autorun in componentDidMount and dispose the listener in componentWillUnmount so that you don't have to reference it in the render method.

class Component extends React.Component {
  constructor(props) {
    super(props);
    this.constructor.displayName = 'BodyClassSync';
  }

  componentDidMount() {
    this.dispose = autorun(() => {
      const { muiOverlay } = this.props.store;

      if (muiOverlay) {
        addClass(window.document.body, 'mod-mui-overlay');
      } else {
        removeClass(window.document.body, 'mod-mui-overlay');
      }
    });
  }

  componentWillUnmount() {
    this.dispose();
  }

  render() {
    return null;
  }
}

Since you are not really doing anything in this component, you could put this autorun directly in your store as well.

like image 114
Tholle Avatar answered Nov 18 '22 22:11

Tholle


Not sure how I missed it, but thanks to @Tholle I've reread the "Reacting to observables" section in the MobX docs and found out the reaction helper was the most suitable solution for me.

While, the reaction was the most fitting for the exact problem I had. The autorun and when helpers are pretty similar in the function and I probably could use them for my use case too.

https://mobx.js.org/refguide/autorun.html

https://mobx.js.org/refguide/when.html

https://mobx.js.org/refguide/reaction.html


This is the code example that shows how to use reaction for anyone that just wants quick copy+paste:

componentDidMount(){
  this._notificationsReactionDispose = mobx.reaction(
    () => this.props.store.notifications,
    (notifications, reaction) => {
      ... the code that runs when "notifications" in my store change
    }
  );
}

componentWillUnmount() {
  this._notificationsReactionDispose();
}
like image 1
Dominik Serafin Avatar answered Nov 18 '22 21:11

Dominik Serafin