Skip to content
This repository has been archived by the owner on Jan 27, 2021. It is now read-only.

Commit

Permalink
Merge pull request #34 from mpeyper/master
Browse files Browse the repository at this point in the history
redux-subspace version 2 - Spliting packages, better middleware support, redux-saga support, improved docs
  • Loading branch information
vivian-farrell authored Aug 15, 2017
2 parents 9693c80 + b22f518 commit dcc7154
Show file tree
Hide file tree
Showing 284 changed files with 34,352 additions and 2,667 deletions.
1 change: 1 addition & 0 deletions .gitignore
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
Expand Down
1 change: 1 addition & 0 deletions .npmignore
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
Expand Down
5 changes: 4 additions & 1 deletion .travis.yml
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
2 changes: 1 addition & 1 deletion LICENSE → LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BSD 3-Clause License

Copyright (c) 2016, IOOF Holdings Limited
Copyright (c) 2017, IOOF Holdings Limited
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
194 changes: 20 additions & 174 deletions README.md
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).
19 changes: 19 additions & 0 deletions docs/Introduction.md
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.
108 changes: 108 additions & 0 deletions docs/Migrating.md
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' }))
```
28 changes: 28 additions & 0 deletions docs/README.md
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)
Loading

0 comments on commit dcc7154

Please sign in to comment.