mxmtsk
mxmtsk

Reputation: 4685

MobX: Observed Component does not rerender after observable change

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";

Upvotes: 3

Views: 16771

Answers (2)

Anantha kumar
Anantha kumar

Reputation: 375

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.

Upvotes: 8

Tareq El-Masri
Tareq El-Masri

Reputation: 2573

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}
/>

Upvotes: 3

Related Questions