Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide Redux support #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

haizi-zh
Copy link

@haizi-zh haizi-zh commented Apr 21, 2016

As a practical architecture of Meteor projects, it would be great if mantra provides a standard way of integrating Redux into mantra projects. The mantra guide does mention that we can manage applications' local states via Redux [ref]. Unfortunately, up till now, there are no "official" and easy ways of doing so in mantra.

Therefore, I have extended the mantra-core package to include Redux support.

First of all, a module might contain the definition of an map of reducer functions:

// definition of a module

const reducers = {
  foo: function(state, action) { return state; }
};

export default {
  actions,
  routes,
  reducers,
  load() {
  }
};

When the App instance "loads" the module by loadModule, as well as dealing with routes and actions, it also collects the module's reducers and adds them to app._reducers.

After all modules have been loaded, in init(), the App tries to combine the reducers, and from which a Redux store is created afterwards. The store will be added to the context, named ReduxStore.

For Redux containers to access the store, I use Provider and connect from the react-redux package to bind React, Redux and mantra together. All these behaviours are implemented in the withRedux function.

Imagine we want to build a Redux container, which maps the app's state to props and provide them to its children presentation components. We can do it easily with just one line of code:

import { withRedux } from 'mantra-core';

// defines mapStateToProps

const ReduxContainer = withRedux(mapStateToProps)(ChildComponent);

Then we can happily use the container:

render() {
  return (
    <ReduxContainer {...props} />
  );
}

I've forked mantra-sample-blog-app and added a Redux counter component to it, which may serve as a demo of the integration of Redux into mantra projects:

https://github.com/haizi-zh/mantra-sample-blog-app/tree/new-mantra-core/client/modules/redux_demo

@genyded
Copy link

genyded commented May 3, 2016

+1

@genyded
Copy link

genyded commented May 10, 2016

Should factor into this middleware, multiple dispatches, and other areas that affect the store before it is created. Here is an alternate consideration (click the arrow to expand it):

Modified redux code

import { createApp } from 'mantra-core';
import initContext from './configs/context';
import { applyMiddleware,  combineReducers, compose, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import reduxMulti from 'redux-multi'
import { batchedSubscribe } from 'redux-batched-subscribe'
// modules
import coreModule from './modules/core';
import commentsModule from './modules/comments';
import reduxDemo from './modules/redux_demo';

// init context
const context = initContext();

const app = ((ctx) => {
  const proto = createApp(ctx);

  const result = {
    _reducers: {},

    loadModule(module) {
      this._checkForInit();

      // Load Redux reducers
      if (module.reducers) {
        const reducers = module.reducers;

        if (typeof reducers !== 'object') {
          const message = `Module's reducers field should be a map of reducers.`;
          throw new Error(message);
        }

        for (const key of Object.keys(reducers)) {
          this._reducers[key] = reducers[key];
        }
      }

      Object.getPrototypeOf(this).loadModule(module);
    },

    loadModules(...modules) {
      modules.forEach((m) => {
        this.loadModule(m);
      });
    },

    init() {
      this._checkForInit();

      const middleware = [
        thunkMiddleware,
        reduxMulti
        //loggerMiddleware
      ];

      const createStoreWithMiddleware = compose(
        applyMiddleware(...middleware),
        //we use Redux devtools via the a Chrome extension from
        //https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
        //if it is installed, the icon will show up in chrome and our app can use it
        window.devToolsExtension ? window.devToolsExtension() : f => f
      )(createStore);

      // Ensure our listeners are only called once, even when one of the above
      // middleware call the underlying store's `dispatch` multiple times
      const finalCreateStore = batchedSubscribe(
        fn => fn()
      )(createStoreWithMiddleware)

      const reducers = this._reducers;

      if (Object.keys(reducers).length > 0) {
        const combined = combineReducers(reducers);
        this.context.ReduxStore = finalCreateStore(combined);
      }

      Object.getPrototypeOf(this).init();
    }
  };

  Object.setPrototypeOf(result, proto);
  return result;
})(context);

app.loadModules(coreModule, commentsModule, reduxDemo);
app.init();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants