Skip to content

Commit

Permalink
Merge pull request #28 from cyclejs-community/run-userland
Browse files Browse the repository at this point in the history
0.3.0 - Export drivers to userland
  • Loading branch information
lmatteis authored Feb 13, 2017
2 parents b249273 + 38e5eb5 commit 5a65c1e
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 78 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 0.3.0 - 2017-02-13

**BREAKING CHANGES**
- `createCycleMiddleware()` no longer takes any arguments. Instead you need to call `Cycle.run` yourself (which can be installed via `npm i -s @cycle/xstream-run`) passing it your `main` function and `drivers` explicitly:

```diff
+import {run} from '@cycle/xstream-run';

-const cycleMiddleware = createCycleMiddleware(main, drivers);
+const cycleMiddleware = createCycleMiddleware();
+const { makeActionDriver, makeStateDriver } = cycleMiddleware;

const store = createStore(
rootReducer,
applyMiddleware(cycleMiddleware)
);

+run(main, {
+ ACTION: makeActionDriver(),
+ STATE: makeStateDriver()
+})
```

`createCycleMiddleware()` apart from returning the middleware function, also has two function properties attached to it; namely the `makeActionDriver()` and the `makeStateDriver()` which you can use accordingly when you call `Cycle.run`.
31 changes: 12 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ Handle redux async actions using [Cycle.js](https://cycle.js.org/).

`npm install --save redux-cycles`

Then use `createCycleMiddleware()` which takes as first argument your `main` Cycle.js function, and second argument the Cycle.js drivers you want to use:
Then use `createCycleMiddleware()` which returns the redux middleware function, but also has two function properties attached to it; namely `makeActionDriver()` and `makeStateDriver()` which you can use accordingly when you call `Cycle.run` (which can be installed via `npm i -s @cycle/xstream-run`).

```js
import { run } from '@cycle/xstream-run';
import { createCycleMiddleware } from 'redux-cycles';

function main(sources) {
Expand All @@ -45,17 +46,22 @@ function main(sources) {
}
}

const cycleMiddleware = createCycleMiddleware(main);
const cycleMiddleware = createCycleMiddleware();
const { makeActionDriver } = cycleMiddleware;

const store = createStore(
rootReducer,
applyMiddleware(cycleMiddleware)
);

run(main, {
ACTION: makeActionDriver()
})
```

## Example

Try out this [JS Bin](https://jsbin.com/govola/10/edit?js,output).
Try out this [JS Bin](https://jsbin.com/bomugapuxi/2/edit?js,output).

See a real world example: [cycle autocomplete](https://github.com/cyclejs-community/redux-cycles/blob/master/example/cycle/index.js).

Expand Down Expand Up @@ -242,8 +248,8 @@ This middleware intercepts Redux actions and allows us to handle them using Cycl

Redux-cycles ships with two drivers:

* `ACTION`, which is a read-write driver, allowing to react to actions that have just happened, as well as to dispatch new actions.
* `STATE`, which is a read-only driver that streams the current redux state. It's a reactive counterpart of the `yield select(state => state)` effect in Redux-saga.
* `makeActionDriver()`, which is a read-write driver, allowing to react to actions that have just happened, as well as to dispatch new actions.
* `makeStateDriver()`, which is a read-only driver that streams the current redux state. It's a reactive counterpart of the `yield select(state => state)` effect in Redux-saga.

```javascript
import sampleCombine from 'xstream/extra/sampleCombine'
Expand All @@ -263,21 +269,8 @@ function main(sources) {
}
```

Here's an example on [how the STATE driver works](https://jsbin.com/kijucaw/7/edit?js,output).

NOTE: If you want to use any other driver aside ACTION and STATE, make sure to have it installed and registered. You can do so at instantiation time, via the `createCycleMiddleware` API.
For example, for the HTTP driver:

```bash
npm install @cycle/http
```

```javascript
import { createCycleMiddleware } from 'redux-cycles';
import { makeHTTPDriver } from '@cycle/http';
Here's an example on [how the STATE driver works](https://jsbin.com/rohomaxuma/2/edit?js,output).

const cycleMiddleware = createCycleMiddleware(main, { HTTP: makeHTTPDriver() });
```
## Utils

### `combineCycles`
Expand Down
17 changes: 12 additions & 5 deletions example/configureStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import { routerMiddleware } from 'react-router-redux';
import rootReducer from './reducers';
import main from './cycle';
import { createCycleMiddleware } from 'redux-cycles';
import {run} from '@cycle/xstream-run';
import {makeHTTPDriver} from '@cycle/http';
import {timeDriver} from '@cycle/time';

const cycleMiddleware = createCycleMiddleware(main, {
Time: timeDriver,
HTTP: makeHTTPDriver()
});

export default function configureStore() {
const cycleMiddleware = createCycleMiddleware();
const { makeActionDriver, makeStateDriver } = cycleMiddleware;

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
Expand All @@ -23,5 +22,13 @@ export default function configureStore() {
)
)
);

run(main, {
ACTION: makeActionDriver(),
STATE: makeStateDriver(),
Time: timeDriver,
HTTP: makeHTTPDriver(),
})

return store;
}
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"react-router": "^2.6.1",
"react-router-redux": "^4.0.5",
"redux": "^3.5.2",
"redux-cycles": "0.2.3",
"redux-cycles": "file:../",
"xstream": "^9.0.0"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "redux-cycles",
"version": "0.2.3",
"version": "0.3.0",
"description": "Bring functional reactive programming to Redux using Cycle.js",
"main": "dist",
"files": [
Expand Down Expand Up @@ -31,10 +31,10 @@
},
"license": "MIT",
"dependencies": {
"@cycle/xstream-run": "^4.0.0",
"xstream": "^9.0.0"
},
"devDependencies": {
"@cycle/xstream-run": "^4.0.0",
"babel-cli": "^6.18.0",
"babel-jest": "^18.0.0",
"babel-preset-es2015": "^6.9.0",
Expand Down
104 changes: 55 additions & 49 deletions src/createCycleMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,13 @@
import {run} from '@cycle/xstream-run'
import xs from 'xstream'

export default function createCycleMiddleware(mainFn, drivers = {}) {
return store =>
next => {
let actionListener = null
let stateListener = null

function actionDriver(outgoing$) {
outgoing$.addListener({
next: outgoing => {
store.dispatch(outgoing)
},
error: () => {},
complete: () => {},
})

return xs.create({
start: listener => {
actionListener = listener
},
stop: () => {},
})
}

const isSame = {}
const getCurrent = store.getState
function stateDriver() {
return xs.create({
start: listener => {
stateListener = listener
},
stop: () => {},
})
.fold((prevState, currState) => {
if (prevState === getCurrent) {
prevState = getCurrent()
}
if (prevState === currState) {
return isSame
}
return currState
}, getCurrent)
.map(state => state === getCurrent ? getCurrent() : state)
.filter(state => state !== isSame)
}

drivers.ACTION = actionDriver
drivers.STATE = stateDriver
run(mainFn, drivers)
export default function createCycleMiddleware () {
let store = null
let actionListener = null
let stateListener = null

const cycleMiddleware = _store => {
store = _store
return next => {
return action => {
let result = next(action)
if (actionListener) {
Expand All @@ -61,4 +19,52 @@ export default function createCycleMiddleware(mainFn, drivers = {}) {
return result
}
}
}

cycleMiddleware.makeActionDriver = () => {
return function actionDriver(outgoing$) {
outgoing$.addListener({
next: outgoing => {
if (store) {
store.dispatch(outgoing)
}
},
error: () => {},
complete: () => {},
})

return xs.create({
start: listener => {
actionListener = listener
},
stop: () => {},
})
}
}

cycleMiddleware.makeStateDriver = () => {
const isSame = {}
return function stateDriver() {
const getCurrent = store.getState
return xs.create({
start: listener => {
stateListener = listener
},
stop: () => {},
})
.fold((prevState, currState) => {
if (prevState === getCurrent) {
prevState = getCurrent()
}
if (prevState === currState) {
return isSame
}
return currState
}, getCurrent)
.map(state => state === getCurrent ? getCurrent() : state)
.filter(state => state !== isSame)
}
}

return cycleMiddleware
}
13 changes: 11 additions & 2 deletions test/createCycleMiddleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@
import { createCycleMiddleware } from '../'
import { createStore, applyMiddleware } from 'redux'
import xs from 'xstream'
import {run} from '@cycle/xstream-run'
jest.useFakeTimers()

function initStore(main, drivers, reducer = null) {
function initStore(main, drivers, reducer = null, r = run) {
const rootReducer = reducer || ((state = [], action) => state.concat(action))
const cycleMiddleware = createCycleMiddleware(main, drivers)

const cycleMiddleware = createCycleMiddleware()
const { makeActionDriver, makeStateDriver } = cycleMiddleware
const store = createStore(
rootReducer,
applyMiddleware(cycleMiddleware)
)

r(main, {
ACTION: makeActionDriver(),
STATE: makeStateDriver()
})

return store
}

Expand Down

0 comments on commit 5a65c1e

Please sign in to comment.