Reactive Meteor Data in Redux's state tree

I’m looking into using Redux with Meteor for a React app.

How would I feed data from Meteor publications into my Redux store while still allowing the state tree to update automatically as the data from Meteor changes in realtime? Who should dispatch the action that triggers the update?

Use Redux Thunk.

Apply the thunk middleware to your store like thus:

import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers.jsx';
import thunkMiddleware from 'redux-thunk';
import { routerMiddleware } from 'react-router-redux';
import { browserHistory } from 'react-router';

const routerMiddle = routerMiddleware(browserHistory);

const middlewares = [
    thunkMiddleware,
    routerMiddle
];

const enhancer = applyMiddleware(...middlewares);

export default function configureStore(initialState) {
    return createStore(rootReducer, initialState, enhancer);
}

Then define an action like this, for example:

export function loadUser() {
    return dispatch => {
        Tracker.autorun(() =>{
            dispatch({
                type: USER_DATA,
                data: Meteor.user()
            });
        });
    }
}

This action is dispatched whenever something changes for the current user (like: logout, login, profile changes…)

And then pass that off to the startup function:

Meteor.startup(function(){
    render((
        <Provider store={store}>
            <Router history={history}>
                <Route path="/" component={App}>
                    <IndexRoute component={Index}/>
                </Route>
            </Router>
        </Provider>
    ),document.getElementById('root'));
    store.dispatch(loadUser());
});

That’s one way to do it. The other way is to use createContainer() and not touch the state at all.

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { createContainer } from 'meteor/react-meteor-data'

const Generic = ({prop, alsoProp, bar}) => (
    <div>
        Generic<br />
        {prop.map( (p) => { return(<p>p._id</p>) } ) }
        {alsoProp}<br />
        <a onClick={bar}>Foo bar baz</a>
    </div>
);

Generic.propTypes = {
    prop: PropTypes.array,
    alsoProp: PropTypes.string,
    bar: PropTypes.func
};

const GenericMeteorContainer = createContainer(({id}) => {
    const sub = Meteor.subscribe("foo");
    return {
         prop: FooCollection.find({_id: id}).fetch() || []
    }
}, Generic);

const mapStateToProps = (state) => {
    return {
        alsoProp: 'Baz',
        id: state.id
    }
};

const mapDispatchToProps = (dispatch) => {
    return {
        bar: () => {
            alert("Test");
        }
    }
};

const GenericContainer = connect(
    mapStateToProps,
    mapDispatchToProps
)(GenericMeteorContainer);

export default GenericContainer

You can’t really have accurate time travel though if you are not putting all the data into the state right? There would be situations where a user’s action depends on the state of the data at the time they dispatched them.

Well, at some point you’ll have to accept that some side-effects can’t be helped.

It’s pretty much that way with anything - strict dogmatism seldom works in the real world.

Redux is a guideline - try to avoid side-effect if you can. Sometimes you can’t.

On the bright side, Apollo completely integrates with redux and time traveling, so this problem will fix itself in a few months!

5 Likes

Or dont put Meteor Data in the Redux store till Apollo is more mature

1 Like

Awesome! Excited for Apollo

Not exactly - if you deal with file uploads then Apollo won’t fix that :slight_smile:

Unless you also want to deal with the headache of an undelete feature.

I’ve taken a halfway house approach on that in our forms based app; I put the details of the record I’m editing into state and then build the UI (an edit form typically) based on that state data.

I’m finding that works pretty well; most of the interesting UI behaviour in our app so far has been related to the state of the record currently being edited (or the record currently selected in a list etc) rather than the contents of e.g. the read only lists of data.