MobX: Observed Component does not rerender after observable change

12,229

Solution 1

As per mobx docs,

The observer function / decorator can be used to turn ReactJS components into reactive components. It wraps the component's render function in mobx.autorun to make sure that any data that is used during the rendering of a component forces a re-rendering upon change. It is available through the separate mobx-react package.

So you need to use the this.props.app.drawer inside render function of observer component to receive reactions from mobx.

Refer this link for more details about how and when mobx reacts.

Solution 2

You need to use observer from mobx-react on your component, also using decorators is the best practice. Also make sure you're using Provider on your Root component

Store

class AppStore {
  @observable drawer = false;
  @action toggleDrawer = () => {
    this.drawer = !this.drawer;
    console.log(this.drawer)
  }
}

Component

const app = new AppStore();
export default app;

@observer 
class AppLayout extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      drawerAnimation: new Animated.Value(0)
    };
  }

  UNSAFE_componentWillReceiveProps(nextProps) {
    console.log('will not get called');
    if (this.props.app.drawer !== nextProps.app.drawer) {
      Animated.timing(this.state.drawerAnimation, {
        toValue: nextProps.app.drawer === true ? 1 : 0,
        duration: 500
      }).start();
    }
  }

  render() {
    console.log("will only be called on first render");
    const translateX = this.state.drawerAnimation.interpolate({
      inputRange: [0, 1],
      outputRange: [0, -(width - 50)]
    });

    return (
      <Provider app={app}>
        <Animated.View style={[styles.app, { transform: [{ translateX }] }]}>
          <View style={styles.appContent}>
            <RouterSwitch />
          </View>
          <View style={styles.appDrawer} />
        </Animated.View>
      </Provider>
    );
  }
}

Trigger

<TouchableOpacity
  onPress={() => {
    app.toggleDrawer();
    // will reflect the new value
    console.log(app.drawer)
  }}
  style={styles.toggle}
/>
Share:
12,229
mxmtsk
Author by

mxmtsk

Freelance Developer Recently founded my own little agency. Designer &amp; developer of WahlSwiper, a non profit app for finding a political party that matches your personal opinions in Germany.

Updated on June 28, 2022

Comments

  • mxmtsk
    mxmtsk almost 2 years

    I have a basic MobX setup in React Native, but my component does not rerender after an observable is getting updated and I can't seem to figure out why.

    react-native 0.56.1; react 16.4.1; mobx 4.5.0; mobx-react 5.2.8

    Store

    class AppStore {
      drawer = false;
      toggleDrawer = () => {
        this.drawer = !this.drawer;
      }
    }
    decorate(AppStore, {
      drawer: observable,
      toggleDrawer: action
    });
    
    const app = new AppStore();
    export default app;
    

    Component

    class _AppLayout extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          drawerAnimation: new Animated.Value(0)
        };
      }
    
      UNSAFE_componentWillReceiveProps(nextProps) {
        console.log('will not get called');
        if (this.props.app.drawer !== nextProps.app.drawer) {
          Animated.timing(this.state.drawerAnimation, {
            toValue: nextProps.app.drawer === true ? 1 : 0,
            duration: 500
          }).start();
        }
      }
    
      render() {
        console.log("will only be called on first render");
        const translateX = this.state.drawerAnimation.interpolate({
          inputRange: [0, 1],
          outputRange: [0, -(width - 50)]
        });
    
        return (
          <Animated.View style={[styles.app, { transform: [{ translateX }] }]}>
            <View style={styles.appContent}>
              <RouterSwitch />
            </View>
            <View style={styles.appDrawer} />
          </Animated.View>
        );
      }
    }
    const AppLayout = inject("app")(observer(_AppLayout));
    

    Trigger (from different component)

    <TouchableOpacity
      onPress={() => {
        app.toggleDrawer();
        // will reflect the new value
        console.log(app.drawer)
      }}
      style={styles.toggle}
    />
    

    EDIT: After some investigation no rerender was triggered because I didn't use the store in the render() method, only in componentWillReceiveProps. This seems super weird to me?

    When I use the store in render, even by just assigning a variable, it starts working:

    const x = this.props.app.drawer === false ? "false" : "true";
    
  • mxmtsk
    mxmtsk over 5 years
    Why can‘t I use decorate? It‘s in the documentation. If you look at my code you see that I‘m using observer. Provider is also in my Root Component
  • Tareq El-Masri
    Tareq El-Masri over 5 years
    Are you using Typescript or Babel?