expo / ex-navigation

Route-centric navigation for React Native
997 stars 201 forks source link

Passing Mobx Stores as component props #259

Open SteveEmmerich opened 7 years ago

SteveEmmerich commented 7 years ago

Is there a way to pass data as props to every route? I want to have several single instance stores passed in as props to my screens. I am used to the basic react-native navigator and just calling each screen like a normal component. renderScene(route, navigator) { return <route.component {...route.stores} navigator={navigator} /> }

Also, I am not using redux instead I am using mobx so if I need to do anything redux specific please explain in detail as I have not used redux before.

Can I do this? please let me know. I am considering dropping exponent from my project because of this.

vonovak commented 7 years ago

you can use provider & inject from mobx-react

rdagger commented 7 years ago

With tab navigation would your wrap the NavigationProvider with a mobx Provider in order to pass props to all the screens? Is there a tab navigation example with mobx?

jkhedani commented 7 years ago

@SteveEmmerich @rdagger Just wrap the <NavigationProvider> element with the mobx <Provider. It also doesn't matter what the structure/type of nav you are using as you'll be injecting the props directly into components that will interact with store. For instance, I'm wrapping a tab layout in a drawer and have no issues with the setup described below:

Let's say you have some store:

import { observable } from 'mobx';

class StoreOne {
  @observable email = 'default@email.com';
  updateEmail = (newEmail) => {
    this.email = newEmail;
  }
}

const store = new StoreOne();
export default store;

In your file that contains your <NavigationProvider>:

import { Provider } from 'mobx-react/native';
import StoreOne from './store/StoreOne.js';
import StoreTwo from './store/StoreTwo.js';
const stores = { StoreOne, StoreTwo }; // In case you have more than one store

<Provider {...stores}>
  <NavigationProvider router={Router}>
     <StackNavigation yourUsualProps />
  </NavigationProvider>
</Provider>

Then in any one of your components you want to be reactive:

import React, { Component } from 'react';
import { TouchableOpacity } from 'react-native';
import { observer, inject } from 'mobx-react/native';

@inject('StoreOne, StoreTwo') // Inject some or all the stores!
@observer
export default class SomeClassRenderedByStack extends Component {

   render() {
     return <TouchableOpacity onPress={() => {this.props.StoreOne.updateEmail('updated@email.com')}}>{this.props.StoreOne.email}</TouchableOpacity>
  }
}

While I'm quite certain this isn't the most efficient way of integrating Mobx, I also don't think it's asking too much in terms of overhead and management. Hope this helps!

benjaminb10 commented 7 years ago

@jkhedani Don't we need to pass stores as props for each child component? Something like that:

<Provider {...stores}>
  <NavigationProvider {...stores} router={Router}>
     <StackNavigation {...stores} yourUsualProps />
  </NavigationProvider>
</Provider>

Or we can access to any ancestry's (parent, grand-parent, etc.) props in a child component?

jkhedani commented 7 years ago

@benjaminb10 The latter assumption is correct. By using @inject you can ensure any component within your <Provider> is reactive to your store - regardless of hierarchy or order in routing. You can confirm this by inspecting props on components that have inject called.

I found this to particularly be useful when creating 'modal' routes, as described here, having users affect app state, then reflecting such effects after popping the route (calling something like this.props.navigator.pop())