mantra-core icon indicating copy to clipboard operation
mantra-core copied to clipboard

Provide Redux support

Open haizi-zh opened this issue 8 years ago • 2 comments

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

haizi-zh avatar Apr 21 '16 15:04 haizi-zh

+1

genyded avatar May 03 '16 04:05 genyded

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();

genyded avatar May 10 '16 13:05 genyded