Giter Club home page Giter Club logo

redux-subspace's Introduction

redux-subspace

Deprecated

This library is no longer being actively maintained.

IOOF has been slowly moving away from the ubiquitous use of Redux as a core piece of our micro-frontend architecture and have been actively replacing the usage of this library with more standard React and JavaScript patterns. Due to some technical constraints, we've also been unable to upgrade to the latest version of the library ourselves for quite some time now, further fuelling our desire to move away from this solution.

At this time, we will be ceasing all maintenance tasks and we recommend that you consider using an alternative library:

If you want to continue using this library, we encourage you to fork this repo and take over maintenance yourself.


npm version npm downloads License: BSD-3-Clause

All Contributors PRs Welcome

Watch on GitHub Star on GitHub

This is a library to help build decoupled, componentized Redux apps that share a single global store.

Installation

npm install --save redux-subspace react-redux-subspace

Quick Start

import React from 'react'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import { namespaced } from 'redux-subspace'
import { SubspaceProvider } from 'react-redux-subspace'
import { TodoApp, todoReducer } from './todoApp'
import { CounterApp, counterReducer } from './counterApp'

const rootReducer = combineReducers({
  todo: todoReducer
  counter1: namespaced('counter1')(counterReducer),
  counter2: namespaced('counter2')(counterReducer)
})

const store = createStore(rootReducer)

const App = () => (
  <Provider store={store}>
    <SubspaceProvider mapState={(state) => state.todo}>
      <TodoApp />
    </SubspaceProvider>
    <SubspaceProvider mapState={(state) => state.counter1} namespace="counter1">
      <CounterApp />
    </SubspaceProvider>
    <SubspaceProvider mapState={(state) => state.counter2} namespace="counter2">
      <CounterApp />
    </SubspaceProvider>
  </Provider>
)

Packages

Upgrading From Version 1 to Version 2

When upgrading to version 2 of Redux Subspace, refer to the migration guide to work through all the breaking changes.

Media

Contributors

Thanks goes to these wonderful people (emojis):

Michael Peyper
Michael Peyper

๐Ÿ’ฌ ๐Ÿ› ๐Ÿ’ป ๐Ÿ“– ๐Ÿ’ก ๐Ÿค” ๐Ÿš‡ ๐Ÿ‘€ ๐Ÿ“ฆ ๐Ÿ“ข โš ๏ธ ๐Ÿ”ง
Jonathan Peyper
Jonathan Peyper

๐Ÿ’ฌ ๐Ÿ’ป ๐Ÿค” ๐Ÿ‘€ โš ๏ธ
Vivian Farrell
Vivian Farrell

๐Ÿค” ๐Ÿ“ฆ ๐Ÿ‘€ ๐Ÿ“ข
Emily Rosengren
Emily Rosengren

๐Ÿ“ข
Morgan Larosa
Morgan Larosa

๐Ÿš‡
Amit Kothari
Amit Kothari

๐Ÿ’ป ๐Ÿ’ก
Riku Rouvila
Riku Rouvila

๐Ÿ’ป ๐Ÿ“– โš ๏ธ
Michael
Michael

๐Ÿ’ป
James Adams
James Adams

๐Ÿ“–
Lee Kyles
Lee Kyles

๐Ÿ’ป โš ๏ธ
Evert Bouw
Evert Bouw

๐Ÿ’ป โš ๏ธ ๐Ÿ“– ๐Ÿ’ก
Paweล‚ Brรณd
Paweล‚ Brรณd

๐Ÿ›
majo44
majo44

๐Ÿ› ๐Ÿ’ป โš ๏ธ
Garth Newton
Garth Newton

๐Ÿ› ๐Ÿ“–
Mateusz Burzyล„ski
Mateusz Burzyล„ski

๐Ÿ”ง
psamusev
psamusev

๐Ÿ›
Jay Phelps
Jay Phelps

๐Ÿ‘€
Mark Erikson
Mark Erikson

๐Ÿ“ข
Nikita
Nikita

๐Ÿ› ๐Ÿ’ป โš ๏ธ
Conrad Buck
Conrad Buck

๐Ÿ’ป โš ๏ธ ๐Ÿ“–
travikk
travikk

๐Ÿ‘€

This project follows the all-contributors specification. Contributions of any kind are welcome!

redux-subspace's People

Contributors

amitkothari avatar andarist avatar byron-wall avatar conartist6 avatar contesini avatar crazy-ivan avatar dependabot[bot] avatar evertbouw avatar james-e-adams avatar jpeyper avatar lkyles777 avatar majo44 avatar mikhailkorotkov-tomtom avatar mpeyper avatar mradionov avatar raybooysen avatar renovate-bot avatar rikukissa avatar theholywaffle avatar vivian-farrell avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-subspace's Issues

Redux v4 compatibility

Redux v4 has been officially released now so we need to ensure that all of our packages are still compatible.

From my understanding, there aren't many breaking changes so it should hopefully just be a case of bumping the version and running the tests to ensure it all still works.

The redux peer dependency should continue to support ^3.0.0 so it will need changing to ^3.0.0 || ^4.0.0 (or however else it's possible to define it that allows consumers to user v3 or v4 without getting a warning).

TypeError: undefined is not an object (evaluating 'store.subspaceOptions')

Is it a bug, feature request or question?

It seems a bug, I always get error below when I try the sample code provided in the documentation.

TypeError: undefined is not an object (evaluating 'store.subspaceOptions')

Which package(s) does this involve?

react-redux-subspace

Input Code

I created a blank react-native app using Expo, then the App.js files has the following content:

import React from 'react';
import { createStore, combineReducers } from 'redux';
import { namespaced } from 'redux-subspace';
import { Provider } from 'react-redux';
import { SubspaceProvider } from 'react-redux-subspace'
import { StyleSheet, Text, View } from 'react-native';

// dummy reducer
const foo = (state = 42) => state

const rootReducer = combineReducers({
  foo: namespaced('foo')(foo),
})

const store = createStore(rootReducer)

const Foo = () => (
  <View style={styles.container}>
    <Text>Open up App.js to start working on your app!</Text>
  </View>
)

export default () => (
  <Provider store={store}>
    <SubspaceProvider mapState={state => state.foo} namespace="foo">
      <Foo />
    </SubspaceProvider>
  </Provider>
);

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Expected Behavior

I should see a screen with the text Open up App.js to start working on your app!

Current Behavior

I get the following error and stacktrace:

[18:16:27] Warning: Failed context type: The context `store` is marked as required in `SubspaceProvider`, but its value is `undefined`.
- node_modules/prop-types/checkPropTypes.js:20:20 in printWarning
- ... 22 more stack frames from framework internals

[18:16:31] TypeError: undefined is not an object (evaluating 'store.subspaceOptions')

This error is located at:
    in SubspaceProvider (at App.js:28)
    in Provider (at App.js:27)
    in _default (at withExpoRoot.js:22)
    in RootErrorBoundary (at withExpoRoot.js:21)
    in ExpoRootComponent (at renderApplication.js:34)
    in RCTView (at View.js:44)
    in RCTView (at View.js:44)
    in AppContainer (at renderApplication.js:33)
- node_modules/redux-subspace/lib/index.js:377:69 in <unknown>
- node_modules/react-redux-subspace/lib/index.js:37:47 in getChildContext
- ... 21 more stack frames from framework internals

If I remove the SubspaceProvider then the app renders, but of course I don't get the subspace.

Your Setup

Here are my dependencies from package.json:

 "dependencies": {
    "expo": "^32.0.0",
    "react": "16.5.0",
    "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
    "react-redux": "^6.0.0",
    "react-redux-subspace": "^3.0.1",
    "redux": "^4.0.1",
    "redux-subspace": "^3.0.1"
  },

Subspaced epic should re-throw an error

I would like to add error handling for all of my epics for this I use

epic(action, store, deps).pipe(
     catchError((error, stream) => {
         / custom error handling
         return stream;   // return stream to make epic alive after any error
})

when I use it outside of subspace all of my errors appear in this ** catchError** callback.
But when epic is executed from subspace epic error is swallowing and catchError is not executing. By this epic is dead but I need keep it alive.

epic which is returning by subspaced should re-throw an error if it happened in original epic

Middleware Compatibility

After the v2 improvements in #34 we are in a better position to support more existing redux middlewares.

This started with redux-saga (which was added in #34), then redux-promise (#36) and now redux-observable (if #37 is merged).

My plan is to try out the most popular middleware and either document that they work, or list any caveats in the redux-subspace documentation (like redux-thunk and redux-promise), and, if required create a compatibility package for them (like redux-saga).

To start with, I want to at least cover the middleware in the ecosystem section of the Redux docs, but also other's as they come on my radar, such as redux-logic which was requested in #30.

This would be a great task for others to get their hands dirty in redux-subspace.

Clean up subspaceWrappers

I think subspaceWrappers is not great and should be split into a few files that better define the functions.

I was thinking of moving them to something like:
getSubState -> utils/subState.js
subStateDispatch -> utils/dispatch.js
namespacedDispatch -> utils/dispatch.js
namespacedReducer -> reducers/namespaced.js

If going for a folder structure like above, I would also move SubspaceProvider.jsx into a components directory and change the extension to just be .js.

Thoughts?

@types in redux-subspace-observable dependencies

bug in redux-subspace-observable

redux-subspace-observable starting from version 3.0.0 contains @types as dependencies and they conflict with built-in typescript libraries.

Possible Solution

Move @types/es6-shim and @types/node to devDependencies

RFC: New wormhole API

What happened?

Recently I lost quite a few hours debugging an issue in a thunk that looked more or less like:

const fetchData = (id) => (dispatch, getState, api) => {
  if (Object.keys(getState()).length === 0) {
    api.get(id).then((data) => dispatch({ type: 'FETCHED_DATA', data })
  }
}

Essentially, the idea here was to only fetch the data only if the state didn't already have any keys.

The reducer looked something like:

const reducer = (state = {}, action) => {
  if (action.type === 'FETCHED_DATA') {
    return { ...state, ...action.data }
  }

  return state
}

This worked fine when developing and testing the component, but would never attempt to fetch the data once it was integrated into a parent component.

Eventually I tracked it back to the use of wormholes in the root application. The wormholes added additional keys to the state, so when checking the length of the keys, it was never empty.

What should have happened?

In the above scenario, I would have preferred if the state was unchanged, particularly as this component did not care about any of the wormholed values. That way, the component would not need to be modified to work within the root app.

Any Ideas?

The issue I see here is the way wormholes get globally applied to all subspaces. This can lead to difficult to find bugs in child components that had no control over what the wormholes, if any, are being applied to their state. The scenario above is an example of this.

I propose changing the wormhole API to be an opt in addition to the components state. In the new API, components would nominate which wormholes, if any, they want to receive, and if no wormholes are nominated the components state is unchanged.

I'm thinking that this could work similarly to React's Context feature where higher in the tree, a component can add values into context, and descendants of the components can opt into received one or more of those values. There is also potential here to improve the developer experience here by providing dev time warnings if the requested wormhole values are not provided by the parent.

Translating this concept to subspaces, the wormhole middleware would be the point higher in the tree to add the values. Currently, multiple wormholes are added a seperate calls to the wormhole middleware. In this proposal the api will change to an object of key to wormhole selector, i.e.:

const store = createStore(reducer, applyMiddleware(
  wormhole((state) => state.value1, 'key1'),
  wormhole((state) => state.value2, 'key2'),
  wormhole((state) => state.value3, 'key3')
))

would become:

const store = createStore(reducer, applyMiddleware(
  wormholes({
    key1: wormhole((state) => state.value1),
    key2: wormhole((state) => state.value2),
    key3: wormhole((state) => state.value3)
  })
))

This is arguably a nicer API than the existing one if there are lots of wormholes, but it does remove the ability to use the selector shorthand that has been adopted in many of the subspace feature (i.e. wormhole((state) => state.value, 'value') can be shortened to just wormhole('value')). I'd like to investigate ways to maintain backwards compatibility with the existing API on this end, but my feeling is that it won't be possible if we want to provide warnings when requested keys are missing.

On the sub-component side placeholder values would be added to it's state to select the values the component wants to be included from the wormholes.

This could be done as a higher-order reducer:

const reducer = combineReducers({ reducer1, reducer2)
const enhancedReducer = withWormholes('key1', 'key3')(reducer)

or as a reducer to combine with others:

const reducer = combineReducers({
  reducer1,
  reducer2,
  wormholes: wormholesReducer('key1', 'key3')
)

There are advantages and disadvantages to both approaches, so I would be keen to hear how you would prefer to use it.

When it comes to using the subspaces, the wormhole middleware would inspect the state of the subspace and replace any of the placeholder values with the actual values from the wormhole selectors (and warn about any that can't be found). There is also potential here to add shape validation (e.g. prop-types) to the wormholed values to ensure the parent is providing them in a usable format for the child, again improving the developer experience.

Pros

  • Declarative
  • Component state is closer to runtime expectations
  • Better developer experience

Cons

  • Inspecting the state in the dev tools would display the placeholder values in their raw format
  • It is (probably) not backwards compatible with the current implementation

One of the biggest differences I see in philosophy between the current implementation and explicit nature of this proposal is that currently, the components don't have to care where the extra values in their state come from. They can be from wormholes, added when mapping the component state in a SubspaceProvider or any other kind of magic you can pull off the get the values there when getState is called, but with the new approach, they can only come from a wormhole. There is a whole series of questions this philosophical change brings in, but I don't have time to go into those now. Perhaps I'll add a following post to ask those soon.

Anyway, how does all this sound? Let me know what you think, ask any questions you want, suggest changes to the proposal, or propose your own changes.

Suggestion to improve global actions from redux middlewares/addons

In using subspace, I discussed a few issues with using react-router in #22

One of the other issues I have had is using react-router-redux and the actions it raises to do navigation.

These need to be global actions, so I have something like this...

import { push as realPush, goBack as realGoBack } from 'react-router-redux'

export const push = path => {
    return {
        globalAction: true,
        ...realPush(path)
    }
}

export const goBack = path => {
    return {
        globalAction: true,
        ...realGoBack(path)
    }
}

react-router-redux exports its action type LOCATION_CHANGE.

It would be nice to be able to register this action type against subspace as a global action so that I don't have to wrap all its actions.

I'm sure there are other action types that always need to be global to work properly.

Also, requiring to know the globalAction: true seems unnecessary and limiting for changing how global actions may work in the future. Adding an asGlobalAction(action) function would clean up the implementation.

Convert tests to Jest

I've been using Jest in my more recent projects and I'm very much enjoying it over mocha/chai/sinon.

I'd like to start converting the tests in each package over to Jest, but would be happy to get help from anyone wanting to contribute.

Feel free to add, remove and/or modify as many test cases as necessary to convert them across.

If you are converting a package, it is probably a good idea to drop a comment here claiming it so no one else wastes their time on it.

Also, don't forget to add yourself as a contributor when you're finished.

Compiled Packaged for use in browser

Hi guys,

I was wondering if there is a compiled package that can be downloaded so people who wanted a play with this on fiddle, or plunkr could do so.

[redux-subspace-observable] DEPRECATION message is trowed by redux-observable

For such simple example:

import 'rxjs/add/operator/map';
import { compose, createStore } from 'redux';
import { createEpicMiddleware, subspaced } from 'redux-subspace-observable';
import { applyMiddleware,  } from 'redux-subspace';

let myEpic = (actions$) => actions$.ofType('A').map(a => {return { type: 'B'};});
let mySubspacedEpic = subspaced(state => state['nested'], 'nested')(myEpic);
let reducer = (state = {}) => {return state; };

let store = createStore(
    reducer,
    compose(applyMiddleware(createEpicMiddleware(mySubspacedEpic)))
);

store.dispatch({type: 'nested/A'});

the DEPRECATION message is trowed by 'redux-observable'

redux-observable | DEPRECATION: calling store.dispatch() directly in your Epics is deprecated and will be removed. Instead, emit actions through the Observable your Epic returns.
https://goo.gl/WWNYSP
    "redux": "^3.7.2",
    "redux-observable": "^0.17.0",
    "redux-subspace": "^2.1.0",
    "redux-subspace-observable": "^2.1.0",
    "rxjs": "^5.5.6",

Best regards.

P.S.
Many thx for your effort on great lib :)

[redux-subspace] Thunk actions not namespaced

Is it a bug, feature request or question?

No sure if it's a bug, but maybe an incomplete feature

Which package(s) does this involve?

namespacedAction from redux-subspace used with redux-thunk middleware

Input Code

https://codesandbox.io/s/m33poxnnky

Expected Behavior

Basically I have some modular component Counter which is connected to a store (it has it's own reducer, actions and is communicating with the store using connect). I include this component somewhere in my app and want to be able to call it's actions from outside by a parent component Page. The problem is that a regular action creator which returns an object { type: String } is namespaced, but a "thunked" action creator is not.

This is namespaced (page/counter/INCREMENT):

// Counter
export const INCREMENT = "INCREMENT";
export const increment = () => ({
  type: INCREMENT
});

// Page
dispatch(namespacedAction("counter")(counterActions.increment()));

This won't be namespaced (INCREMENT):

// Counter
export const incrementAsync = () => dispatch => {
  setTimeout(() => {
    dispatch(increment());
  }, 1000);
};

// Page
dispatch(namespacedAction("counter")(counterActions.incrementAsync()));

I would expect my thunked actions be namespaced as well.

Current Behavior

I can see from source of namespacedAction it simply namespaces "pure" action creators like { type: String }. When I have a "thunked" action creator, it returns a function and is simply by-passed by namespacedAction and I end up with none of my "pure" action creators namespaced inside of "thunked" action.

Possible Solution

I was able to workaround it by creating a subspace for a child component from a parent component, but it looks like an overkill if I will have to do it in for every call. Also it seems that I must have to know of the entire store structure from the root.

https://codesandbox.io/s/5kq4zq3qxp

// Store setup
let store;

const getStore = () => store;

store = createStore(
  rootReducer,
  applyMiddleware(thunk.withExtraArgument({ getStore }))
);

// Page action
export const incrementCounterAsync = () => (
  dispatch,
  getState,
  { getStore }
) => {
  const substore = subspace(state => state.page.counter, "page/counter")(
    getStore()
  );
  substore.dispatch(counterActions.incrementAsync());
};

Maybe there you could advice some easier way of getting it done without knowing the app structure.

Context

I wanted to control child components from parent components by calling actions of child components, because some things are pretty difficult to do only using props and I would like to have some imperative form of manipulating the child components.

Your Setup

Everything is the latest version except react-redux because it looks like the latest version is not supported yet.

"react": "^16.6.0",
"react-dom": "^16.6.0",
"react-redux": "5",
"react-redux-subspace": "3.0.1",
"redux": "^4.0.1",
"redux-subspace": "^3.0.1",
"redux-thunk": "2.3.0"

Removing Subspaced Reducer

Is it a bug, feature request or question?

Bug

Which package(s) does this involve?

redux-subspace

Expected Behavior

replaceReducer should cause subspace to throw

Current Behavior

When using replaceReducer from redux that removes a subspaced reducer, I get an error from redux-suspace at verifyState because the mapState function now returns null.

Context

I have an SPA that allows you to add widgets. As a user adds a widget, I call replaceReducer to add in the new reducer for the widget. When the user closes the widget, I call replaceReducer again to remove the widget.

package version(s)
redux 7.0.3
redux-subspace 3.0.1
etc.

Multiple calls to mapState per render

The mapState function is being called multiple times for each render.

The problem can be seen here and here where it is call once for validation and again to create the sub-state.

This was discovered when playing around with static typing (come join the discussion in #15).

Should lib folder be checked in?

Should the lib folder be checked in? I would have thought lib would be generated as part of some CI build and pushed into npm, but would not need to exist in git.

I'm happy to remove it, just wondered if there was reason for it being there.

Limited compatibility with redux-sagas

As identified in #8, there is only partial compatibility with redux-sagas.

There appear to be no issues until the namespacing feature is used. The actions being dispatched by sagas are not passing through our wrapper and the namespaces are not being applied so the reducers are discarding them.

I have taken a look at the problem myself, but without much exposure to sagas before, I'm struggling to find a solution so any help would be much appreciated.

Poor feedback if mapState returns undefined

While making the examples for the repo, I found I'd often make a mistake and map the state to undefined by accident (e.g. map to non-existent property of state). This was difficult to track down as the error is raised by a connected component within the sub-component, or by a generic undefined error.

I think there needs to be a check on the result of the mapState prop to ensure it is not undefined before proceeding.

I would also suggest that the check should only be done if running in a non production environment as this is more of an aid for developers when setting up the state mappings than something we'd want users to see in their consoles.

Problem with middleware after upgrading to version 2.2.0

I upgraded to version of redux-subspace 2.2.0 and started catch an error

Subspace epic couldn't find the store. Make sure you've used createEpicMiddleware from redux-subspace-observable
Error: Subspace epic couldn't find the store. Make sure you've used createEpicMiddleware from redux-subspace-observable

Previous one - 2.1.1 worked correct

My environment is

    "redux": "^3.7.2",
    "redux-freeze": "^0.1.5",
    "redux-logger": "^3.0.6",
    "redux-observable": "^0.17.0",
    "redux-subspace": "^2.1.1",
    "redux-subspace-observable": "^2.1.1",

Also I use Angular 5 and Angular redux

    "@angular/common": "5.2.1",
    "@angular/compiler": "5.2.1",
    "@angular/core": "5.2.1",
    "@angular/forms": "5.2.1",
    "@angular/http": "5.2.1",
    "@angular/platform-browser": "5.2.1",
    "@angular/platform-browser-dynamic": "5.2.1",
    "@angular/platform-server": "5.2.1",
    "@angular/router": "5.2.1",
    "@angular-redux/router": "^7.0.0",
    "@angular-redux/store": "^7.1.0",

Incompatability with Redux 4 in react-redux-subspace/redux-subspace

I am trying to used subspaced from the react-redux-subspace and am currently getting an error

...my-project-path/node_modules/redux-subspace/src/index.d.ts
(34,21): Namespace '"...my-project-path/node_modules/redux/index"' has no exported member 'GenericStoreEnhancer'.
<Route
  path="something"
  component={subspaced((state) => state["module-namespace"], "module-namespace")(Component)}/>

My Setup

package version(s)
redux 4.0.0
redux-subspace 2.5.0
react-redux-subspace 2.5.0

Not sure if this is already known and part of #87 or not, but if you are aware you can feel free to just close this issue

Better first impression of the docs

There have been a few reports that the docs not being very friendly upon initial viewing:

it would appear that they are ok once you get into it and start using it, but my concern is that there are people not even giving it a go after a bad first impression.

From the reddit post, I probed "s_tec" for what his main issue was and I got the impression it was the lack of any code snippets in the READMEs (possibly the main repo or the packages or both?).

I'm conscious that the package READMEs are what get included in the npm packages, so they need to be useful as if there was no other packages included (excluding redux-subspace as it's assumed you would be using that in conjunction with another one).

The part I'm struggling with most is what level of detail to put into the main repo's README. * Should it include react examples, in which redux-subspace's subspace function would not be shown (showing subspaced from react-redux-subspace instead), or would you keep that one purely redux-subspace?

  • What about middleware? If you aren't demonstrating middleware, you don't actually need redux-subspace's applyMiddleware function at all.

  • Is it fine as it is and we expect the viewer to navigate to the package READMEs before they give up?

Any and all ideas and suggestions are welcome (encouraged even), so please feel free to leave them here or put a pull request with an improvements you think would make it better.

@rikukissa, I'd be really interested in what you would have preferred to see on initial viewing.

How to use with reselect?

Could you provide an example how to use redux-subspace with reselect?
I have a problem with react-redux-subspace and reselect.
I use very complex selectors which based on root scope and have to base on a local scope as well.

Pass extra props down to wrapped component

I have been using redux-subspace with react-router to isolate my pages from one another.

This has worked great as it has allowed me to develop each page as almost an entirely seperate application.

How I have this setup is as follows (simplified from my actual code)

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { syncHistoryWithStore, routerMiddleware, routerReducer as routing } from 'react-router-redux'
import { SubspaceProvider, namespaced } from 'redux-subspace'
import { SubPage1, reducer as subpage1 } from './SubPage1'
import { SubPage2, reducer as subpage2 } from './SubPage2'

const reducer = combineReducers({
    routing,
    subpage1: namespaced(subpage1, 'subpage1'),
    subpage2: namespaced(subpage2, 'subpage2'),
})
const middleware = applyMiddleware(routerMiddleware(browserHistory))
const store = createStore(reducer, middleware);
const history = syncHistoryWithStore(browserHistory, store)

const SubspacedSubPage1 = () =>
    <SubspaceProvider mapState={state => state.login} namespace='subPage1'>
        <SubPage1 />
    </SubspaceProvider>

const SubspacedSubPages2 = () =>
    <SubspaceProvider mapState={state => state.characters} namespace='subpage2'>
        <SubPage2 />
    </SubspaceProvider>

ReactDOM.render(
    <Provider store={store}>
        <Router history={history}>
            <Route path="/sub-page-1" component={SubspacedSubPage1} />
            <Route path="/sub-page-2/:someParam" component={SubspacedSubPage2} />
        </Router>
    </Provider>,
    document.getElementById('root')
)

SubPage1.js

import React from 'react';
import { connect } from 'react-redux';

const SubPage1 = () =>
    <div>
        <p>Hello From SubPage1</p>
    </div>

const mapStateToProps = (state) => {
    return {
        ...state
    }
}

export default connect(mapStateToProps)(SubPage1)

SubPage2.js

import React from 'react';
import { connect } from 'react-redux';

const SubPage2 = (someParam) =>
    <div>
        <p>Hello From SubPage2 with someParam={someParam}</p>
    </div>

const mapStateToProps = (state, ownProps) => {
    return {
        ...state
        ...ownProps.params
    }
}

export default connect(mapStateToProps)(SubPage2)

This works great, except for the someParam part. React router provides the params to the components props, which is SubspaceProvider and not the wrapped component.

I did manage to get around this with the following changes to index.js

const SubspacedSubPages2 = (props) =>
    <SubspaceProvider mapState={state => state.characters} namespace='subpage2'>
        <SubPage2 {...props}/>
    </SubspaceProvider>

However, I can also see other use cases where the parent component wants to pass params down to the underlying component, but either doesn't have access to the child to do so (like my case above where it was wrapped by another component) or it has imported a subspaced component and doesn't realise it cannot pass props (its is probably a bad design to expect props and also subspace yourself, but if you did want that its current not doable).

I propose that SubspaceProvider passes any extra props it receives down to the child component.

Support alternate store keys.

Redux-subspace should configuration for attaching to alternate redux store keys.

This will support usage in situations where there may actually be more than one Redux store, though that is something that this library will hopefully remove the need for.

Also it will support situations like mine, where I am building a set of reusable, redux-bound wrapper components which return rendering control to their parent context. In this case in order for the application to function properly my subspace for the wrappers needs to have its own store key, because using SubspaceProvider on the store context key would prevent the wrapped application components from accessing the non-subspaced store.

Any ideas on subspacing components with render props?

I have certain components that have render props. Here's an example:

<SubspaceProvider mapState={state => state.myTable} namespace="MY_TABLE">
  <Table
    rows={[...]}
    renderRow={data => {
      return (
        <TableRow>
          <FooCell foo={data.foo}>
          <BarCell bar={data.bar}>
        </TableRow>
      );
    }}
  />
</SubspaceProvider>

My table component is in a subspace, so that it has a reducer which can handle its own selection logic, but those rows are being rendered inside the subspace, and they need to have their parents redux context.

Right now I subspace each row using subspace((_, rootState) => rootState), but this clearly sucks. For one thing it makes the strong assumption that the parent state selector is the root state selector, which is rather limiting. For a second, there's no way to do anything similar with action namespaces.

Do the authors have any thoughts as to how this might be supported, or if it is worth supporting?

Using a prefilled state with namespaced reducers causes loss of the default store values.

Hello!
I faced some issues when I was trying to use namespaced reducers while creating the redux store with prefilled data.

I've made a simple repo to illustrate the problem.

Briefly, the bug is appearing when you have some reducer, lets say viewReducer:

let defaultState = {
  design: 'flat',
  detalizationLevel: 'small'
};

export default function viewReducer(state, action) {
  state = {
    ...defaultState,
    ...state
  }

  ...

Which is appearing as a part of the settingsReducer:

const settingsReducer = namespaced('settings')(
  combineReducers({
    view: viewReducer,
    ...another reducers belonging to the settings section...
  })
);

const appReducer = combineReducers({
  settings: settingsReducer,
  ...there can be other reducers for other parts of the application...
})

So right now the store hierarchy is something like that:

{
  settings: {
    view: {
      design: 'flat',
      detalizationLevel: 'small'
    },
    ...
  },
  ...
}

So the issue itself: If you try to create redux store with prefilled state:

const prefilledState = {
  settings: {
    view: {
      design: 'material'
    }
  }
}

const store = createStore(appReducer, prefilledState);

Then it will result into the store with missing default values:

store.getState();
# => {
       settings: {
         view: {
           design: 'material'
         }
       }
     }

As you can see the detalizationLevel field is not present.

Possible solution

This issue exists because of the processAction() function's code.
According to the code of the redux's createStore() - redux/src/createStore.js function:

// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })

This action is a special unique redux-specific action which type starts with @@redux/....
This will trigger all reducers because it wouldn't find appropriate case for this action and it will initialze store with default values which are specified in the reducers.
But according to the code of processAction() - redux-subspace/src/actions/processAction.js function:

import hasNamespace from '../actions/hasNamespace'
import isGlobal from '../actions/isGlobal'

const processAction = (namespace) => (action, callback, defaultValue) => {
    if (!namespace || isGlobal(action)) {
        return callback(action)
    } else if (hasNamespace(action, namespace)) {
        return callback({...action, type: action.type.substring(namespace.length + 1)})
    } else {
        return defaultValue
    }
}

export default processAction

As this redux-specific actions are not namespaced, first two conditions in processAction() function will be false so all namespaced reducers will return ONLY(!!!) provided prefilledState and that will lead to some missing default values untill the user duplicates them in the prefilledState.

So I made a quick fix for it in the corresponding PR.
But I am afraid a bit of the fact that if it was written like so, then there were a reasons for it. So, please, tell me if I am missing something.

Context

Situation with prefilledState is quite common when using server side rendering with React, when you fetched data from the API and need to generate the store from which yout React-application will take the data to render.

Your Setup

package version(s)
redux 4.0.1
redux-subspace 3.0.0

Thank you guys for your really helpfull library!

Discussion: Scoping via function composition vs. scope as a delimited string or array

I can see two ways of tracking the subspacing in the component tree. Both involve altering the redux store provided in the context. The first approach uses function composition, while the second uses a scope string or array.

I think one of the challenges for us, as we discuss these concepts, is finding the language for things. Perhaps we should start another discussion about names?

First approach:

import PropTypes from 'prop-types'
import {Component} from 'react'

class SubspaceProvider extends Component {
  static propTypes = {
    namespace: PropTypes.string.isRequired
  }

  static contextTypes = {
    store: PropTypes.shape({
      dispatch: PropTypes.function.isRequired,
      getState: PropTypes.function.isRequired
    }).isRequired
  }

  static childContextTypes = {
    store: PropTypes.shape({
      dispatch: PropTypes.function.isRequired,
      getState: PropTypes.function.isRequired
    }).isRequired
  }

  getChildContext () {
    const {namespace} = this.props
    const {store} = this.context

    return {
      store: {
        ...store,
        dispatch: (action) =>
          store.dispatch({
            ...action,
            type: `${namespace}/${action.type}`
          }),
        getState: () => store.getState()[namespace]
      }
    }
  }
}

Second approach:

import {get} from 'lodash/fp'
import PropTypes from 'prop-types'
import {Component} from 'react'

class SubspaceProvider extends Component {
  static propTypes = {
    namespace: PropTypes.string.isRequired
  }

  static contextTypes = {
    store: PropTypes.shape({
      dispatch: PropTypes.function.isRequired,
      getState: PropTypes.function.isRequired,
      rootStore: PropTypes.shape({
        dispatch: PropTypes.function.isRequired,
        getState: PropTypes.function.isRequired
      }),
      scope: PropTypes.array
    }).isRequired
  }

  static childContextTypes = {
    store: PropTypes.shape({
      dispatch: PropTypes.function.isRequired,
      getState: PropTypes.function.isRequired,
      rootStore: PropTypes.shape({
        dispatch: PropTypes.function.isRequired,
        getState: PropTypes.function.isRequired
      }),
      scope: PropTypes.array.isRequired
    }).isRequired
  }

  getChildContext () {
    const {store} = this.context

    const scope = store.scope || []
    const rootStore = store.rootStore || store

    scope.push(this.props.namespace)

    return {
      store: {
        ...store,
        dispatch: (action) =>
          rootStore.dispatch({
            ...action,
            type: `${scope.join('/')}/${action.type}`
          }),
        getState: () => get(scope.join('.'))(rootStore.getState()),
        rootStore,
        scope
      }
    }
  }
}

Each of these approaches works fine for state scoping and action prefixing. I'm curious what pros and cons of each approach people see. I see a clear winner when it comes to supporting middleware, but let's begin the discussion with the more general case.

Questions and thoughts

This lib looks fantastic. I have some questions about best practices when using it though. When building a fractal component, what is the preferred way for the parent to interact with the children? Do you ever tinker with child state from a parent reducer? Like if the child needs to be reset back to initial state, would you do that in the parent reducer. Or, would you make the child component a controlled component and have the parent interact with it through value and onValueChanged props, for example? Or maybe there's another way that parent->child communication should happen?

The other thing I'm wondering about is redux-logic support. I know it's not the most popular side-effect lib, but if you haven't looked at it, it's wonderful. No strange generator or observable syntax to deal with. You can write your side effects using async/await if you want, and the finished code ends up reading so clean. I've used sagas and epics, and I'm moving all of that code over to logics now. Since all logics are created with the createLogic function, it might be quite easy to wrap that in some way to add namespacing support.

Anyway, thank you for your work here.

Unit Test SubspaceProvider

I would like to have a unit test around the SubspaceProvider component but I don't have much experience testing react components, especially those that use features like context and Redux.

Any assistance (either pointers or pull requests) on doing this would be much appreciated.

TypeError: Object doesn't support property or method 'startsWith' [redux-subspace]

Is it a bug, feature request or question?

Bug, exception is thrown in console when using Internet Explorer 11

Which package(s) does this involve?

redux-subspace

Expected Behavior

No exception is thrown.

Current Behavior

The following exception is thrown:
TypeError: Object doesn't support property or method 'startsWith'

const isGlobal = (action) => !action.type || action.globalAction === true || action.type.startsWith(REDUX_PREFIX)

Possible Solution

Don't use String.prototype.startsWith in order to support older environments

package version(s)
redux 4.0.1
redux-subspace 3.1.0
Internet Explorer 11

Diagram to better explain how to use this library

I feel that it's not clear in the readme what this library does and why you'd use it. I want to create a diagram that shows the relationship between an exported component, a parent app and global store. I also want to highlight the limitation of react-redux that inspired this library.

Create a redux-subspace logo

I've previously stated that I would like to get a (cool) logo for this library, but having zero artistic ability myself, I'm reaching out to anyone who does.

There was one attempt a while ago with some notes in it of what I'm after but here are some general ideas (you don't have to use all of them):

  • minimalistic, clean design
  • space themed (stars, rockets, planets, moons, etc.)
  • tied in to the Redux logo would be nice (e.g. redux-saga), keeping in mind we integrate with a lot of other libraries of the redux ecosystem as well, so being able to switch out the redux logo for their logo is a win
  • could use the "IOOF greens" for colour inspiration
  • ignore everything I've said and make something awesome

Add optional scope parameter when namespacing

This is going to get a bit confusing to explain, but I'll do my best.

I've run into an issue where I want to add a namespaced reducer for a subspaced component, but the reducer will be a sibling of parent component's reducer, instead of a child.

Component structure:

  • App
    • Parent (namespace: 'parent')
      • Child (namespace: 'child')

Reducer structure:

  • Root
    • Parent (namspace: 'parent')
    • Child (namespace: 'child')

The reason why the reducer is a sibling and not being combined into the parent's reducer is because the reducers are being added dynamically using the `replaceReducer' which, so far, I have only been able to get reducers to be added to the top level of the store.

The problem is, when an action (e.g. 'SET_VALUE') is dispatched from the child, as it breaks out of each subspace, the namespace gets prepended so the final action type is 'parent/child/SET_VALUE' but the action is ignore by both reducers:

  • The parent reducer will successfully remove the 'parent' namespace but none of it's reducers are listening for the resulting action type ('child/SET_VALUE').
  • The child reducer is looking for an action that starts the 'child\' so it won't pass on an action that starts with 'parent/child'.

I propose the namespacing functionality be expanded to include an scope parameter to apply the namespace to. If passed undefined, if will fallback to the default behaviour of inferring the scope from the nesting hierarchy. I also propose that the same functionality be available anywhere a namespace can be provided (namspace, subspaced, SubspaceProvider)

It could be implemented as:

  1. a seperate optional parameter
    • namespace(reducer, 'child', 'parent')
    • subspaced(mapState, 'child', 'parent')(Component)
    • <SubspaceProvider mapState={mapState} namespace='child' scope='parent'>
  2. optionally accepting an object for the `namespace parameter
    • namespace(reducer, { id: 'child', scope: 'parent' })
    • subspaced(mapState, { id: 'child', scope: 'parent' })(Component)
    • <SubspaceProvider mapState={mapState} namespace={{ id: 'child', scope: 'parent' }}>

Another thing to consider what are acceptable values for scope. I think it should accept:

  1. a string
    • most basic way to add a scope
    • namespace(reducer, { id: 'child', scope: 'parent' }) would result in the reducer being namespaced to 'parent/child'
  2. an array
    • can add multiple levels without knowing the 'prefix/...' convention
    • namespace(reducer, { id: 'child', scope: ['other', 'parent'] }) would result in the reducer being namespaced to 'other/parent/child'

To enable this, it may be necessary for the subspaced store to carry around it's current namespace (or scope?) to be accessed by others when needing to identify the current scope.

If there are no objections to this, I will start working on this soon, or if someone else want's to take a crack, let me know and you can have first dibs.

Namespaced reducers and normal reducers seems to not working both together

Hi everybody,

I have a question about redux-subspace.

I have a component that is using subspace for one reducer. It's working ok.
But another component is also using subspace for the same reducer BUT it also need another reducer.

So, it's like subspace map the state of the reducer that it manages but the other reducer is never accessible (in mapStateToProps function of my component).

I'm wondering if it exists a trick to make both normal reducer and namespaced reducers working together.

PS: I've try to sub-combine my reducers that have to work together but same sad result:

export default combineReducers({
  router: routerReducer,
  userProfile: namespaced(user, 'userProfile'),
  userSettings: namespaced(combineReducers({ user, setting }), 'userSettings'),
  user,
  signup,
  whishlist,
  setting
});

Here, the user reducer is well accessible in my component but the setting reducer functions are not available :(

Thanks

EDIT: in fact, the functions of the reducers are obvioulsy callable (because of import) but the component's props are never updated :(

RFC: Components sharing state between subspaces

We recently had a requirement come up where two related components needed to share state with each other. Both components came from the same micro-frontend package and shared the same reducer and actions. The problem was that one of the components was nested inside another subspace. The component hierarchy was something like:

  • app
    • summary
      • component1
    • component2

redux-subspace is quite good (IMHO) at encapsulating the the summary state from the component2 state, so consequently the resulting state structure was:

  • appState
    • summaryState
      • componentState
    • componentState

with the store being created like:

const summaryReducer = combineReducers({
  // other reducers
  componentState: namespaced('component')(componentReducer)
})

const reducer = combineReducers({
  summary: namespaced('summary')(summaryReducer),
  // other reducers
  componentState: namespaced('component')(componentReducer)
})

const store = createStore(reducer /*, ... */)

Note: component1 and component2 are expecting to share the state in the same reducer, which was not occurring.

The hack solution we used to get around this was to wrap component2 in a subspace to fake out the summary layer. The simplified version looked something like:

<App>
  <SubspaceProvider mapState={state => state.summaryState} namespace="summary">
    <Summary>
      {/* lots of other stuff */}
      <SubspaceProvider mapState={state => state.componentState} namespace="component">
        <Component1 />
      </SubspaceProvider>
    </Summary>
  </SubspaceProvider>
  {/* lots of other stuff */}
  <SubspaceProvider mapState={state => state.summaryState} namespace="summary">
    <SubspaceProvider mapState={state => state.componentState} namespace="component">
      <Component2 />
    </SubspaceProvider>
  </SubspaceProvider>
</App>

and the store is created as:

const summaryReducer = combineReducers({
  // other reducers
  componentState: namespaced('component')(componentReducer)
})

const reducer = combineReducers({
  summary: namespaced('summary')(summaryReducer),
  // other reducers
})

const store = createStore(reducer /*, ... */)

This worked and we were able to move on, but it bothers me that we had to fake a layer of the state to allow the components to work as intended. We were also lucky that we were actually able to push component2's state down a layer to make it work. I can quite easily see a situation where both components are in entirely different levels in the tree, e.g.

  • app
    • header
      • component1
    • summary
      • component2

Our solution could not work in this scenario.

As soon as this occurred, @jpeyper and I started spit-balling how redux-subspace could help out in scenarios like this and we came up with this.

The idea is that you could flag a subspace as transparent which will behave as a normal subspace for any component mounted and action dispatched within it, but if a subspace created from it it will use it's parent's state and namespace instead of it's own, as if it doesn't exist to the nested subspaces.

Effectively, it's just following React's advice for these types of scenarios and allowing the common state to be lifted up to to the common ancestor.

Going back to the above example, it would change to:

<App>
 <SubspaceProvider mapState={state => state.summaryState} namespace="summary" transparent={true}>
   <Summary>
     {/* lots of other stuff */}
     <SubspaceProvider mapState={state => state.componentState} namespace="component">
       <Component1 />
     </SubspaceProvider>
   </Summary>
 </SubspaceProvider>
 {/* lots of other stuff */}
 <SubspaceProvider mapState={state => state.componentState} namespace="component">
   <Component2 />
 </SubspaceProvider>
</App>

and the store could now be created as:

const summaryReducer = combineReducers({
  // other reducers
})

const reducer = combineReducers({
  summary: namespaced('summary')(summaryReducer),
  // other reducers
  componentState: namespaced('component')(componentReducer)
})

const store = createStore(reducer /*, ... */)

I did a quick proof of concept locally and it can be done, so now I have some questions:

  1. Do we want this?

  2. Is transparent the correct term for this?
    It came about because a child subspace would look straight it to it's parent when resolving state and namespaces

  3. Should it be all or nothing?
    Under the current proposal, transparent={true} would result in all nested subspaces ignoring the it. Instead, we could use an array of namespaces (regex?) to give more control, e.g.:

    <App>
      <SubspaceProvider mapState={state => state.summaryState} namespace="summary" transparentTo={['component' /*, ... */]}>
        <Summary>
          {/* lots of other stuff */}
          <SubspaceProvider mapState={state => state.componentState} namespace="component">
            <Component1 />
          </SubspaceProvider>
        </Summary>
      </SubspaceProvider>
      {/* lots of other stuff */}
      <SubspaceProvider mapState={state => state.componentState} namespace="component">
        <Component2 />
      </SubspaceProvider>
    </App>

    If we allow regex, perhaps transparent={true} could be used as an alias for transparentTo={[/.*/]}.

I'm interested in hearing any thoughts on the above, alternative solutions and answers to my questions.

Cheers.

Using `applyMiddleware` more than once per store does not work

What happened?

Using redux-subspace's applyMiddleware more than once when creating a store causes some of the middleware to not be applied to subspaces.

import { createStore } from 'redux'
import { applyMiddleware } from 'redux-subspace'
import thunk from 'redux-thunk'
import wormhole from 'redux-subspace-wormhole'

const store = createStore(reducer, compose(
  applyMiddleware(thunk),
  applyMiddleware(wormhole('example'))
))

What should have happened?

All middleware should be applied to subspaces, regardless of how many calls to applyMiddleware are used.

Any ideas?

Admittedly, it's a pretty uncommon use case to want to use applyMiddleware more than once.

For me, it was trying to create a react-storybook decorator that would mount the component within a SubspaceProvider and have some default middleware applied while also allowing the additional middleware to be applied.

We managed to work around the issue without needing two calls to applyMiddleware but it did highlight this issue to me.

I think it could be overcome by checking to see if the store already has an enhancer in the subspaceOptions and composing it with the new enhancer, but that is just a hunch (I haven't looked too closely at it).

[subspace-saga]

Many times my saga uses data from global scope with connection with local scope. Is there any option to select things from the root store?

Add static types

After attending a talk at the YOW! conference I was compelled to try out flow and typescript to add more confidence for me when writing javascript code (I come from a mostly Java/C# background).

I would like some comment and review of:
flow
typescript

I'm particularly interested in your thoughts on which you prefer and why. When I get time, I'll add my thoughts.

Is this something we actually want for this library? I do, but this was more of a thought experiment for me personally and I'm sure there are improvements that can be made to my first attempts.

Just adding the types identified a few issues in the existing code, which I'll issues for (probably with pull requests - they're pretty minor) so I definitely see a benefit of it.

[react-native] console.assert is not a function

It looks like when react-native app is running without remote debugger, app crash due to lack of assert function in the console object. I would suggest adding polyfill, or just check if assert is available before running it.

Discussion: Recommended API for user-defined modules

I stumbled upon this the other day:

http://antontelesh.github.io/architecture/2016/03/16/fractal-architecture.html

My main takeaway was that a fractal component should have an api that's something like:

export create = () => ({ view, reducer })

Extending the idea to include the subspace concept, it seems like a possible best practice to build modules might be to use a pattern like:

export const createComponent = id => // return subspaced component

export const createReducer = id => // return subspaced reducer

The great thing here is that the parent component and parent reducer are still in charge of the namespace id, but they know nothing of the namespacing implementation. In some ways, this is like a duck for fractal components. Thoughts?

Dynamic namespaces

Suppose that I have some original React/Redux app that already works. Then, I need to modify the application so that I can run many "instances" on the same page.

This package seems nearly ideally suited to that purpose -- but the namespacing seems designed for a fixed set of heterogeneous sub-applications with different action types rather than a homogeneous dynamic list of sub-applications sharing the same action types. In particular, redux-subspace accomplishes namespacing by changing the name of the action, whereas I would like to instead either "stuff" extra properties into the payload or (perhaps more cleanly) add an extra "context" property alongside the type and payload.

Is there a sensible way to do something like that within the context of react-subspace? If not, would it be reasonable to extend react-subspace to accommodate that use case, or does that fall outside the scope of what this package ought to do?

(I wish that there were a stable-ish "future" version of react-redux; that would make the whole thing easy!)

[redux-subspace-observable] Update usage docs for `createEpicMiddleware`

Previously, it was an option to use the default createEpicMiddleware function from the redux-observable library, but in version ^2.2.0, only the createEpicMiddleware wrapper provided by redux-subspace-observable is supported.

The docs still say that the other method is possible:

In order for redux-observable middleware to work, it must only be applied to the root store and not to every subspace. You can use the applyToRoot utility yourself, but redux-subspace-observable provides a middleware that does that for you

This needs to be updated to better describe the new behaviour.

Allow store to be passed to SubspaceProvider as props

I was looking through the slides @vivian-farrell shared from the MelbJS event and I have a suggestion for an alternative way to provide the store to the subspace.

On slide 44, the following snippet is shown:

ReactDOM.render(
    <Provider store={reduxStore}>
        <ThemeProvider theme={theme}>
            {React.createElement(scope.component(), {configuration: scope.configuration})}
        </ThemeProvider>
    </Provider>
element[0]);

If the intention is to eventually have all the micro-frontends wrapped in a SubspaceProvider then it may end up looking something like:

ReactDOM.render(
    <Provider store={reduxStore}>
        <ThemeProvider theme={theme}>
            <SubspaceProvider mapState={state => state.component}>
                {React.createElement(scope.component(), {configuration: scope.configuration})}
            </SubspaceProvider>
        </ThemeProvider>
    </Provider>
element[0]);

At this point, the only reason to use Provider in this scenario is for SubspaceProvider to use. I think it would be nice if it could be written as:

ReactDOM.render(
    <ThemeProvider theme={theme}>
        <SubspaceProvider store={reduxStore} mapState={state => state.component}>
            {React.createElement(scope.component(), {configuration: scope.configuration})}
        </SubspaceProvider>
    </ThemeProvider>
element[0]);

SubspaceProvider would need to be changed to look for a store either in props or in the context before applying the subspacing logic.

We would need to decide what would take preference if both were found. My gut feeling is that props should override context as it is more deliberately declared, but I'm sure arguments could be made for the alternative.

Thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.