Skip to content

Approach for using nested stores in a decoupled application #306

@emmenko

Description

@emmenko

Hey guys,

I have a complex use-case in mind and I'd like to pick your brain for correctly approaching this.
Before explaining the problem, I know there have been related questions and I know there is no plan on supporting this, but hear me out.

Overview

Imagine I have an application with some main routes

<Route path="/" component={Application}>
  <Route path="dashboard" component={Dashboard} />
  <Route path="posts" component={PostsList} />
  <Route path="account" component={Account} />
</Route>

We can consider the Application container as simply the scaffolding component, which might include a TopNavigation and a LeftNavigation, and renders the child route as the main view.
So each main route is basically rendered as the main view.
That's pretty standard.

The Application also has a "global" state, which might include user information, token for authentication and other stuff necessary for the overall functionality of the app. Of course this includes the related reducers, actions and so on.
The root component of the app also is wrapped around a Provider, like it's supposed to be.

The idea

Now image that all those main route components (Dashboard, PostsList, ...) are considered as modules (or plugins, or extensions, whatever name you like) and are basically separated from the main application.
Imagine a concept similar to babel or eslint, where you have the core and plugins that can be developed outside.

The idea is that I can develop my Dashboard module indipendently from the main application. For simplicity let's say that each module simply exports its routes.

// dashboard
const DashboardRoot = props => (<h1>{'Dashboard'}</h1>)
const DashboardRoute = () => (
  <Route path="dashboard" component={DashboardRoot} />
)
export default DashboardRoute

The problem

Here some facts:

  • each module can have its own local store with reducers, actions and so on
  • modules are decoupled from each other
  • the main application doesn't know about what's going on in each module
  • modules can use and access the "global" state of the application (e.g. user)

Given that, I think it's fair to assume that I would use, in each module, an own Provider.

const DashboardRoot = props => (
  <Provider store={store}>
    <h1>{'Dashboard'}</h1>
  </Provider>
)
const DashboardRoute = () => (
  <Route path="dashboard" component={DashboardRoot} />
)
export default DashboardRoute

At this point there is a problem though. A store already exists in the context as it was defined by the root Provider of the main app.

As suggested in previous questions regarding this topic (nested providers), passing manually the store as a prop is not an option, as e.g. I want to use all the goodnesses of connect.

A possible solution

Ideally speaking, I think we could replace the Provider in each module with some sort of local provider for each module. Let's call this LocalProvider.

const DashboardRoot = props => (
  <LocalProvider localStore={localStore}>
    <h1>{'Dashboard'}</h1>
  </LocalProvider>
)
const DashboardRoute = () => (
  <Route path="dashboard" component={DashboardRoot} />
)
export default DashboardRoute

Now there are 2 different stores in context:

  • the global application store provided by the main app
  • the local module localStore provided in each module

So module components can access both stores, one using the normal connect and one using a localConnect.

The difference between the 2 Provider and connect are basically the name of the context object: store vs localStore.

Conclusions

If we agree on that, we might make Provider and connect somehow configurable to define the name of the store object, falling back to the default store.

I'd like to hear your thoughts now, whether this is a correct approach, whether I'm missing some piece of the puzzle or whether there are "better" alternatives.

Thanks! :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions