Modular design for mobile and web development

Let's talk about

  1. React-Native is dope
  2. Sharing is caring
  3. (Re-)introducing modules
  4. Now code it

React-Native is dope

IOS + Android = React-Native

  • Development in JavaScript
  • Component approach based on React
  • Same codebase for Android and IOS development
  • One language to rule them all, one language to find them, one language to bring them all and in the darkness bind them.

React-Native is JS

  • You can use (almost) all your favorite libraries
  • Redux is still your store
  • Unit testing use your favorite libraries: mocha, chai, jest, ...
  • You can share code between your web and mobile

SHaring is caring

What we can share BETWEEN WEB AND MOBILE

  • Most of community libraries
  • Most of home-made libraries
  • Resource client: React-Native provides the Fetch API
  • Reducers, actions, sagas, ...

What we can't SHARE

  • Libraries using DOM
  • Router
  • Views

But there are some issues :(

  • How to architect our mobile and web applications ?
  • How to handle web/mobile oriented libraries ?
  • How to keep it maintainable ?

(RE-)Introducing moduleS

Your codebase

src
├── actions
│ ├── product.js
│ └── product.spec.js
├── containers
│ ├── Product.js
│ └── Product.spec.js
├── constants
│ ├── product.js
│ └── product.spec.js
├── components
│ ├── List.js
│ └── List.spec.js
├── reducers
│ ├── product.js
│ └── product.spec.js
└── app.js

Splitting your code in module

A module will contain all code relative to a FEATURE:
  • Feature's  views (containers, components)
  • Feature's  actions
  • Feature's  reducer(s)
  • Feature's saga(s)
  • Feature's  route(s)
  • ...

Module's ADVANTAGES

  • All your related stuff in same place
  • More maintainable, especially when you
    have a huge codebase
  • Easily reusable

Structure

src
├── modules
│ └── product-list
│   ├── actions
│   │ ├── index.js
│   │ └── index.spec.js
│   ├── components
│   │ ├── List.js
│   │ └── List.spec.js
│   ├── containers
│   │ ├── Product.js
│   │ └── Product.spec.js
│   ├── reducers
│   │ ├── index.js
│   │ └── index.spec.js
│   └── index.js
└── app.js

You don't share anything on day 1

You start by coding a web or a mobile app: avoid creating 9823 node_modules in the first month if you don't share anything at start.

Using module pattern in your src/ is a good thing.  It allows you to move to node_modules easily when the time comes.

How can you use your module ?


Your application:

  • Needs to load some views
  • Needs to load module's routes
  • Needs to add module's reducers
  • Needs to add module's saga(s)

Use import ?

We can import components and routes from module.

All modules have to know where data are stored.
Module can't assume their data will be in a specific
path of the store.

We need a place where we handle
path of module's reducers.

registry pattern

Your application's code
import registerProductList from './modules/product-list';
registerProductList();

Module's code
import reducers from './reducers';
import sagas from './sagas';

const registerProductList = () => {
  registerReducer('myModuleName', reducers);
  registerSagas(sagas);
};

export default registerProductList;

dependencies


Advantages

  • Your module can ignore where it will be used: your mobile app, your web app, your back-office app, ...
  • Easily removable
  • Good pattern to access to a module's reducer from another module

Now CODE IT

Reducers, Sagas:
redux-register-module

A simple module to register your module reducers/sagas

  • Expose registerModuleReducer(key, reducer)
    to register a reducer
  • Expose getModuleState(key, state) to get module
    state
  • Expose a registerModuleSaga(saga)
  • Lightweight

Plug

products/index.js
import { registerModuleReducer } from 'react-register-module';
import reducer from './reducer.js';

export default () => {
  registerModuleReducer('products', reducer);
}
src/store/configureStore.js
import registerProducts from 'products/reducer';
import createRootReducer from '../reducers';

const configureStore = (initialState) => {
  registerProducts(); // Register the products reducer

  const rootReducer = createRootReducer();
};
        

Plug (2)

src/reducers.js
import { combineReducers } from 'redux';
import { getModuleReducerKey, getReducers } from 'redux-register-module';

const createRootReducer = combineReducers({
  [getModuleReducerKey()]: combineReducers(getReducers()),
});

export default createRootReducer;

Play

users/components/web/UserList.js
import { getModuleState } from 'react-register-module';
import { connect } from 'redux';

const mapStateToProps = (state) => ({
  products: getModuleState('products', state).list,
  users: getModuleState('users', state).list,
});

...

And of course, you can use reselect if you want
import { getModuleState } from 'react-register-module';

const productsSelector = state => getModuleState('products', state).list;
...

Routes / Entrypoint

  • Your mobile app have to instantiate first containers
  • Your web app needs module's routes


Your applications


Handle views

  • A module contains mobile and web views
  • A good way to start is to export views in different files
    • web.js for React components
    • mobile.js for React Native components

Module structure


products
├── ...
├── components
│ ├── mobile
│ │ ├── ProductIconList.js
│ │ └── ProductIconList.spec.js
│ └── web
│   ├── ProductList.js
│   ├── ProductList.spec.js
│   ├── Product.js
│   └── Product.spec.js
├── ...
├── actions.js
├── constants.js
├── mobile.js
└── web.js

A graph (Views)

Inter-module imports


import { ProductList } from 'products/web'; // Web components
import { ProductIconList } from 'products/mobile'; // Mobile components
import { getProducts } from 'products/actions'; // Actions
import { PRODUCT_SIZE } from 'products/constants'; // Constants

Another graph



Conclusion

  • Start with "src" modules, move to npm when you scale
  • Your app imports the module and registers it
  • Your module ignores where data are and use registry
  • Separate web and mobile components but they are in the same module, using same actions and same reducers
  • Don't make micro-modules, it will be pain in the ass to maintain

Thank you for your attention


Clément Danjou
Software Consultant

https://clement.danjou.io/
Twitter @cledanjou