This repository has been archived by the owner on Jan 27, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from mpeyper/master
redux-subspace version 2 - Spliting packages, better middleware support, redux-saga support, improved docs
- Loading branch information
Showing
284 changed files
with
34,352 additions
and
2,667 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
node_modules | ||
npm-debug.log | ||
lerna-debug.log | ||
.nyc_output | ||
coverage | ||
.vscode | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
.git/ | ||
node_modules | ||
npm-debug.log | ||
docs/ | ||
examples/ | ||
.nyc_output | ||
coverage | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
language: node_js | ||
node_js: | ||
- "6" | ||
- "8" | ||
script: | ||
- npm run bootstrap | ||
- npm run test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,188 +1,34 @@ | ||
Redux Subspace Library | ||
----------------------- | ||
# redux-subspace | ||
|
||
[![npm version](https://img.shields.io/npm/v/redux-subspace.svg?style=flat-square)](https://www.npmjs.com/package/redux-subspace) | ||
[![npm version](https://img.shields.io/npm/v/redux-subspace.svg?style=flat-square)](https://www.npmjs.com/package/redux-subspace) | ||
[![npm downloads](https://img.shields.io/npm/dm/redux-subspace.svg?style=flat-square)](https://www.npmjs.com/package/redux-subspace) | ||
[![License: MIT](https://img.shields.io/npm/l/redux-subspace.svg?style=flat-square)](LICENSE) | ||
[![License: MIT](https://img.shields.io/npm/l/redux-subspace.svg?style=flat-square)](LICENSE.md) | ||
|
||
This is a library to create subspaces for Redux connected React components. It's designed to work with Provider from the [react-redux](https://github.com/reactjs/react-redux) bindings. | ||
This is a library to create isolated sub-applications within a Redux application. | ||
|
||
The MelbJS presentation that launched this library - [Scaling React and Redux at IOOF](http://www.slideshare.net/VivianFarrell/scaling-react-and-redux-at-ioof). | ||
|
||
## What it does | ||
For a Redux connected React component, SubspaceProvider allows you to present a sub-view of the state to the component, allowing it to be ignorant of parent state structure. This means you can reuse these components in multiple parts of your app, or even multiple applications that have different store structures. | ||
|
||
Actions dispatched from components can be automatically namespaced to prevent them from being picked up by unrelated reducers that inadvertently use the same action types. Actions dispatched inside thunks executed by Redux-thunk middleware will be automatically namespaced. | ||
|
||
## Use this library if... | ||
* You are using a single global Redux store, but would like to create decoupled and sharable Redux components. | ||
* You want actions dispatched from these components to not be picked up by reducers in other components (i.e. avoid action cross talk). | ||
* You are using a micro frontend architecture and want to achieve decoupling of your micro frontends for parallel development. | ||
|
||
## How to use | ||
|
||
### In parent component/app | ||
|
||
``` | ||
npm i --save redux-subspace | ||
``` | ||
|
||
Combine component's reducer | ||
## Installation | ||
|
||
```sh | ||
npm install --save redux-subspace | ||
``` | ||
import { combineReducers } from 'redux' | ||
import { reducer as subComponent } from 'some-dependency' | ||
|
||
... | ||
## Documentation | ||
|
||
const reducer = combineReducers({ subComponent }) | ||
``` | ||
|
||
Wrap sub-component with provider | ||
|
||
``` | ||
import { SubspaceProvider } from 'redux-subspace' | ||
import { SubComponent } from 'some-dependency' | ||
... | ||
* [Introduction](/docs/Introduction.md) | ||
* [Basics](/docs/basics/README.md) | ||
* [Advanced](/docs/advanced/README.md) | ||
* [API Reference](/docs/api/README.md) | ||
|
||
<SubspaceProvider mapState={state => state.subComponent}> | ||
<SubComponent /> | ||
</SubspaceProvider> | ||
``` | ||
## Sub-Packages | ||
|
||
The root state of the store is also provided as a second parameter to `mapState` as well. This can be useful for accessing global in nested components (e.g. configuration). | ||
* [`react-redux-subspace`](/packages/react-redux-subspace): React bindings compatible with `react-redux` | ||
* [`redux-subspace-saga`](/packages/redux-subspace-saga): Utilities for integrating with `redux-saga` | ||
* [`redux-subspace-wormhole`](/packages/redux-subspace-wormhole): Middleware for exposing additional state to subspaces | ||
|
||
``` | ||
<SubspaceProvider mapState={(state, rootState) => ({ ...state.subComponent, configuration: rootState.configuration })> | ||
<SubComponent /> | ||
</SubspaceProvider> | ||
``` | ||
## Upgrading From Version 1 to Version 2 | ||
|
||
### In sub-component | ||
When upgrading to version 2 or Redux Subspace, refer to the [migration guide](/docs/Migrating.md) to work through all the breaking changes. | ||
|
||
Export reducer | ||
|
||
``` | ||
const initialState = { | ||
value: "store value" | ||
} | ||
export default function reducer(state = initialState, action) { | ||
... | ||
return state | ||
} | ||
``` | ||
|
||
Use in mapStateToProps | ||
|
||
``` | ||
const mapStateToProps = state => { | ||
return { | ||
value: state.value, | ||
parentValue: state.root.value | ||
} | ||
} | ||
``` | ||
|
||
An additional `root` node is added to the state which will reflect the root state. | ||
|
||
#### Higher-Order Component | ||
|
||
The `subspaced` HOC can be used to wrap components you do not want to directly wrap in `jsx`. An [example](./examples/react-router/index.jsx) of when this might be useful is when setting `Route` components for `react-router`. | ||
|
||
``` | ||
import { subspaced } from 'redux-subspace' | ||
import { SubComponent } from 'some-dependency' | ||
const SubspacedSubComponent = subspaced(state => state.subComponent)(SubComponent) | ||
``` | ||
|
||
### Namespacing | ||
|
||
Namespacing sub-components allows multiple instances of the component to exist on the same page, without the actions affecting each other's state. | ||
|
||
To namespace the sub-component both the provider and the reducer need to be namespaced by the parent component/app. The `type` of any dispatched namespaced actions will be in the format `givenNamespace/originalType`. | ||
|
||
#### Provider | ||
|
||
``` | ||
import { SubspaceProvider } from 'redux-subspace' | ||
import { SubComponent } from 'some-dependency' | ||
## Media | ||
|
||
... | ||
<SubspaceProvider mapState={state => state.subComponent}, namespace='myComponent'> | ||
<SubComponent /> | ||
</SubspaceProvider> | ||
``` | ||
|
||
#### Reducers | ||
|
||
``` | ||
import { combineReducers } from 'redux' | ||
import { namespaced } from 'redux-subspace' | ||
import { reducer as subComponent } from 'some-dependency' | ||
... | ||
const reducer = combineReducers({ subComponent: namespaced(subComponent, 'myComponent') }) | ||
``` | ||
|
||
#### Global Actions | ||
|
||
Occasionally you may have actions that need to go beyond your small view of the world. | ||
|
||
If you have control over the action creator, passing your action to the `asGlobal` function before it is dispatched will ensure your action does not get namespaced. | ||
|
||
``` | ||
import { asGlobal } from 'redux-subspace' | ||
const globalActionCreator = globalValue => { | ||
return asGlobal({ type = "GLOBAL_ACTION", globalValue}) | ||
} | ||
``` | ||
|
||
This method gives you more control over if and when an action should be treated as global. | ||
|
||
To exclude all instances of an action type from namespacing, the `GlobalActions.register` function can be used. | ||
|
||
``` | ||
import { GlobalActions } from 'redux-subspace' | ||
GlobalActions.register("GLOBAL_ACTION") | ||
const globalActionCreator = globalValue => { | ||
return { type = "GLOBAL_ACTION", globalValue} | ||
} | ||
``` | ||
|
||
This is particularly useful when using actionsCreators of dependencies that are unaware that they are being dispatched within a namespaced subspace, such as the navigation actions from [react-router-redux](https://github.com/reactjs/react-router-redux) (see our [example](./examples/react-router-redux/index.jsx) for more details). | ||
|
||
#### Higher-Order Component | ||
|
||
The `subspaced` HOC also supports namespacing. | ||
|
||
``` | ||
import { subspaced } from 'redux-subspace' | ||
import { SubComponent } from 'some-dependency' | ||
const SubspacedSubComponent = subspaced(state => state.subComponent, 'myComponent')(SubComponent) | ||
``` | ||
|
||
### Thunks | ||
|
||
Any actions dispatched by your thunks are wrapped with the same subspace and namespacing rules as standard actions. If they use the `getState` function, they will receive the state provided by the subspace. | ||
|
||
### Nesting Subspaces | ||
|
||
When nesting subspaces, the `root` node will reflect the top most root state. Namespaced actions and reducers will be prepended with the parent's namespace, if provided. | ||
|
||
## Examples | ||
|
||
Examples can be found [here](./examples). | ||
|
||
## Caveats | ||
|
||
* You cannot use `root` as a field in your state. It will be replaced with the root of the state tree. Sorry. | ||
* We assume you are using redux-thunk if you dispatch a function as an action. If you're not, please submit a pull request adding compatibility with your middleware of choice, without breaking thunk for us. | ||
The MelbJS presentation that launched this library - [Scaling React and Redux at IOOF](http://www.slideshare.net/VivianFarrell/scaling-react-and-redux-at-ioof). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Introduction | ||
|
||
Redux Subspace is an extension to Redux. If you are not familiar with Redux itself, please take the time read [their documentation](http://redux.js.org/) first. | ||
|
||
## Motivation | ||
|
||
Redux revolutionised state management in single-page javascript applications, however, in large, complex applications, it doesn't take long before there are lots of nested reducers and refactoring the state structure can have a rippling effect throughout the application. | ||
|
||
A common solution to solve this is to break the large application up into multiple sub-applications. The [Redux documentation](http://redux.js.org/docs/recipes/IsolatingSubapps.html) suggests using multiple stores to do this, but often there is some part of the state that is global to all the sub apps as well as their own local state. | ||
|
||
Redux Subspace attempts to solve this problem in a different way. Rather than multiple stores, a single store is used, so there is still [a single source of truth](http://redux.js.org/docs/introduction/ThreePrinciples.html#single-source-of-truth), and sub-stores are created with a selector to present a slice of the state to the sub-application. | ||
|
||
Another common issue that occurs in large applications is how to [reuse action and reducer logic](http://redux.js.org/docs/recipes/reducers/ReusingReducerLogic.html) for multiple sections of the state. Redux Subspace has the ability to namespace actions and reducers to prevent action type cross-talk and state changes in unrelated sections of the application. | ||
|
||
## Core Concepts | ||
|
||
Redux Subspace attempts to follow all of the [Core Concepts of Redux](http://redux.js.org/docs/introduction/CoreConcepts.html) itself without drastically changing the way you write a Redux application. In fact, an application using a subspace as it's store should be mostly oblivious to the fact that it isn't just a regular Redux store. The only thing that should be aware of the subspace is the parent application that created it. | ||
|
||
In that regard, it is important to note that subspaces are arbitrarily nestable, so an application that creates a subspace for a sub-application could be a sub-application in it's own right. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# Migrating | ||
|
||
There were some significant breaking changing introduced in version 2 of `redux-subspace`. These were mostly caused by seperating the library up into multiple packages for use with different frameworks, or by utilising the new middleware pipeline for subspaces. | ||
|
||
The following sections describe the major changes from `redux-subspace` version 1 to 2. | ||
|
||
## React bindings | ||
|
||
The React binding have moved from the `redux-subspace` package to `react-redux-subspace`. | ||
|
||
Firstly, you must install the new package: | ||
|
||
```sh | ||
npm i -S react-redux-subspace | ||
``` | ||
|
||
### `SubspaceProvider` | ||
|
||
```diff | ||
-import { SubspaceProvider } from 'redux-subspace' | ||
+import { SubspaceProvider } from 'react-redux-subspace' | ||
``` | ||
|
||
### `subspaced` | ||
|
||
```diff | ||
-import { subspaced } from 'redux-subspace' | ||
+import { subspaced } from 'react-redux-subspace' | ||
``` | ||
|
||
## Namespaced Reducers | ||
|
||
The `namespaced` higher-order reducer has had it's signature changed to be more composable with other higher-order reducers: | ||
|
||
```diff | ||
import { combineReducers } from 'redux' | ||
import { namespaced } from 'redux-subspace' | ||
|
||
const reducer = combineReducers({ | ||
- myReducer: namespaced(myReducer, 'myNamespace') | ||
+ myReducer: namespaced('myNamespace')(myReducer) | ||
}) | ||
``` | ||
|
||
## `redux-thunk` | ||
|
||
In version 1, `redux-thunk` was implicilty supported as a hard-coded behaviour. With the introduction of a middleware pipeline to redux-subspace, `redux-thunk` must now be applied as middleware to the store. | ||
|
||
Assuming you already had `redux-thunk` installed and applied to your root store, you only need change it to apply the middleware to subspaces as well. | ||
|
||
To apply the middleware to subspaces, an alternative `applyMiddleware` function is provided by `redux-subspace`: | ||
|
||
```diff | ||
-import { createStore, applyMiddleware } from 'redux' | ||
+import { createStore } from 'redux' | ||
+import { applyMiddleware } from 'redux-subspace' | ||
import thunk from 'redux-thunk' | ||
|
||
const store = createStore(reducer, applyMiddleware(thunk)) | ||
``` | ||
|
||
Middleware applied to subspaces will also be applied to the root store, so there is not need to apply it twice. | ||
|
||
## The `root` node | ||
|
||
The `root` node is no longer appended to the subspace state. The same functionality can be obtained from the `redux-subspace-wormhole` middleware. | ||
|
||
Firstly, install the middleware: | ||
|
||
```sh | ||
npm i -S redux-subspace-wormhole | ||
``` | ||
|
||
Then we must apply the middleware to the subspaces using `applyMiddleware` provided be `redux-subspace`: | ||
|
||
```diff | ||
import { createStore } from 'redux' | ||
+import { applyMiddleware } from 'redux-subspace' | ||
+import wormhole from 'redux-subspace-wormhole' | ||
|
||
-const store = createStore(reducer) | ||
+const store = createStore(reducer, applyMiddleware(wormhole((state) => state, 'root'))) | ||
``` | ||
|
||
## Global Actions | ||
|
||
The `GlobalActions.register` function has been replaced by a `globalActions` middleware: | ||
|
||
```diff | ||
import { createStore } from 'redux' | ||
-import { GlobalActions } from 'redux-subspace' | ||
+import { applyMiddleware, globalActions } from 'redux-subspace' | ||
|
||
-GlobalActions.register('GLOBAL_ACTION') | ||
|
||
-const store = createStore(reducer) | ||
+const store = createStore(reducer, applyMiddleware(globalActions('GLOBAL_ACTION'))) | ||
``` | ||
|
||
The `asGlobal` action wrapper has also been renamed to `globalAction` to be more consistent with the new action wrappers: | ||
|
||
```diff | ||
-import { asGlobal } from 'redux-subspace' | ||
+import { globalAction } from 'redux-subspace' | ||
|
||
-store.dispatch(asGlobal({ type: 'GLOBAL_ACTION' })) | ||
+store.dispatch(globalAction({ type: 'GLOBAL_ACTION' })) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Table of Contents | ||
|
||
* [Read Me](/README.md) | ||
* [Introduction](/docs/Introduction.md) | ||
* [Basics](/docs/basics/README.md) | ||
* [Getting Started](/docs/basics/GettingStarted.md) | ||
* [Creating Subspaces](/docs/basics/CreatingSubspaces.md) | ||
* [Namespacing](/docs/basics/Namespacing.md) | ||
* [Usage With React](/docs/basics/UsageWithReact.md) | ||
* [Advanced](/docs/advanced/README.md) | ||
* [Nesting Subspaces](/docs/advanced/NestingSubspaces.md) | ||
* [Global State](/docs/advanced/GlobalState.md) | ||
* [Scoping Actions](/docs/advanced/ScopingActions.md) | ||
* [Middleware](/docs/advanced/middleware/README.md) | ||
* [redux-thunk](/docs/advanced/middleware/redux-thunk.md) | ||
* [redux-saga](/docs/advanced/middleware/redux-saga.md) | ||
* [Custom Middleware](/docs/advanced/middleware/CustomMiddleware.md) | ||
* [API Reference](/docs/api/README.md) | ||
* [subspace](/docs/api/subspace.md) | ||
* [applyMiddleware](/docs/api/applyMiddleware.md) | ||
* [namespaced](/docs/api/namespaced.md) | ||
* [globalAction](/docs/api/globalAction.md) | ||
* [namespacedAction](/docs/api/namespacedAction.md) | ||
* [applyToRoot](/docs/api/applyToRoot.md) | ||
* [applyToNamespaceRoot](/docs/api/applyToNamespaceRoot.md) | ||
* [applyToChildren](/docs/api/applyToChildren.md) | ||
* [globalActions](/docs/api/globalActions.md) | ||
* [Migrating from v1 to v2](/docs/Migrating.md) |
Oops, something went wrong.