inProgress-team / react-native-meteor

Meteor Reactivity for your React Native application :)
MIT License
693 stars 210 forks source link

createContainer not re-running with variable change (react-navigation, redux + react-native-meteor) #219

Open acomito opened 7 years ago

acomito commented 7 years ago

I'm trying to use react-navigation with redux (and react-native-meteor). This is my current setup which follows react-navigation advice. My entry point file is below:

src/index.js

import React from 'react';
import Meteor, { createContainer } from 'react-native-meteor';
import { Loading } from './components/common';
import { addNavigationHelpers } from 'react-navigation';
import { Provider, connect } from "react-redux";
import getStore from "./store";
import Routes from "./routes";
import * as actions from './actions';

//connect to meteor
Meteor.connect('ws://localhost:3000/websocket');

//create nav reducer
const navReducer = (state, action) => {
    const newState = Routes.router.getStateForAction(action, state);
    return newState || state;
};

const mapStateToProps = (state) => {
  return { 
    nav: state.nav,
    bizStages: state.bizStages,
    categories: state.categories,
    selectedLocation: state.selectedLocation
  }
};

// connect Routes Component with redux, remember you need babelrc with "transform-decorators-legacy" to use decorators
@connect(mapStateToProps)
class RoutesWithNavigationState extends React.Component {
    render() {
      return (
          <Routes navigation={addNavigationHelpers({ ...this.props, state: this.props.nav  })} />
      );
    }
}

// create store and then rest of this file is similar to setup in Spencer's react-native-meteor-boilerplace: https://github.com/spencercarli/react-native-meteor-boilerplate
const store = getStore(navReducer);

const App = (props) => {
  return (
    <Provider store={store}>
      {props.status.connected === false || props.loggingIn 
        ? <Loading {...props} /> 
        : <RoutesWithNavigationState {...props} />
      }
    </Provider>
  );
}

App.propTypes = {
  status: React.PropTypes.object,
  user: React.PropTypes.object,
  loggingIn: React.PropTypes.bool,
};

export default createContainer(() => {
  return {
    status: Meteor.status(),
    user: Meteor.user(),
    loggingIn: Meteor.loggingIn(),
  };
}, App);

store.js

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import getRootReducer from "./reducers";

const getStore = (navReducer) => {
    const store = createStore( getRootReducer(navReducer), undefined, applyMiddleware(thunk) );
    return store;
}

export default getStore;

reducers/index.js

import { combineReducers } from "redux";

const BizStageReducer = (state=['idea', 'early', 'existing'], action) => {
    return state;
};

const CategoriesReducer = (state=['support', 'finance', 'incentives'], action) => {
    return state;
};

const LocationsReducer = (state=null, action) => {
    console.log(action)
    switch(action.type) {
        case 'select_location':
            return state = action.payload;
        default:
            return state;
    }
};

const getRootReducer = (navReducer) => {
    return combineReducers({
        nav: navReducer,
        bizStages: BizStageReducer,
        categories: CategoriesReducer,
        selectedLocation: LocationsReducer
    });
};

export default getRootReducer;

There is no login right now. My initial screen is just a list (<MainScreen />, which you can see below). At the top of the page there is a picker (<LocationSelector />) that allows you to select a location. It runs a redux action. That action runs correctly, and <RoutesWithNavigationState /> from src/index.js reruns with new props, but my createContainer that wraps MainScreen (see below) is not re-run...

So I'm not 100% sure how to set this up. It'd be great if all actions and redux store values can just be dropped down via the navigation prop instead of sprinkling @connect everywhere I could just have it in src/index.js... but child components are not updating when properties on the navigation object change.

Should I use @connect on createContainer to get it to work? am I just missing some redux-related thing? Its getting a little crazy between all these HOCs...(1) hoistNonReactStatic so I can use static in my class, (2) createContainer from react-native-meteor and now I'm wondering if I have to wrap it again in @connect so that I have access to the redux variables in createContainer?

import React from 'react';
import Meteor, { createContainer } from 'react-native-meteor';
import { Image, TouchableHighlight, View, ListView } from 'react-native';
import { Container, Content, Grid, Picker, Col, Card, CardItem, Left, Body, Text, Button, Icon, Right } from 'native-base';
import hoistNonReactStatic from 'hoist-non-react-statics';
import { Loading } from '../../components/common';
import { connect } from "react-redux";
import * as actions from '../../actions';
import { ResourceCard  from './ResourceCard'
import { LocationSelector } from './LocationSelector'

class MainScreen extends React.Component {
  static navigationOptions = {
    title: 'Home',
    tabBar: { label: 'Home' }
  };
  render(){
    let { navigation, items, resourceItemsReady } = this.props;
    let { navigate, dispatch } = navigation;
    let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

    if (!resourceItemsReady) {
       return (
            <Container style={{backgroundColor: '#f5f5f5'}}>
                <Loading />
            </Container>
        ); 
    }
    return (
        <Container style={{backgroundColor: '#f5f5f5'}}>
            <Content style={{padding: 10}}>
              <Text>Show me resources near...</Text>
              <LocationSelector
                selectLocation={(value)=>dispatch({ type: 'select_location', payload: value})} 
              />
              <ListView 
                  dataSource={ds.cloneWithRows(items)}
                  renderRow={(rowData) => <ResourceCard item={rowData} navigation={navigation} />}
              />
            </Content>
        </Container>
    );

  }
}

const container = createContainer(params => {
    let resourceItemsSub = Meteor.subscribe('ResourceItems.searchResults'); //this is where I would like to pass in different parameters like categories, search terms, etc
    return {
      resourceItemsReady: resourceItemsSub.ready(),
      items: Meteor.collection('ResourceItems').find(),
      navigation: params.navigation
    };
}, MainScreen);

export default hoistNonReactStatic(container, MainScreen);

Seems maybe similar to this disucssion: https://github.com/inProgress-team/react-native-meteor/issues/84