diff --git a/README.md b/README.md index 8cdcc0e..a10b538 100644 --- a/README.md +++ b/README.md @@ -5,37 +5,36 @@ state trooper # Example Usage -Call `StateTrooper.patrol` in your route handler/main app entry point +Call `StateTrooper.patrolRunLoop` in your route handler/main app entry point ```javascript -go(function*() { - let component = React.renderComponent( - , - document.querySelector('body') - ); - const cursorChan = StateTrooper.patrol({ - // describe the state for the page - state: { - serverReport: null, - bio: null, - activity: null - }, - - // describe the fetchers and persisters for each piece of state - // fetchers and persisters are functions that should return channels - dataStore: { - 'serverReport': { fetcher: serverReportFetcher }, - 'bio': { fetcher: bioFetcher, persister: bioPersister }, - 'activity': { fetcher: activityFetcher } - } - }); +const config = { + // describe the state for the page + state: { + serverReport: null, + bio: null, + activity: null + }, - let cursor; - while(cursor = yield take(cursorChan)) { - // update the component cursor prop everytime it changes - component.setProps({ cursor: cursor }); + // describe the fetchers and persisters for each piece of state + // fetchers and persisters are functions that should return channels + dataStore: { + 'serverReport': { fetcher: serverReportFetcher }, + 'bio': { fetcher: bioFetcher, persister: bioPersister }, + 'activity': { fetcher: activityFetcher } } +}; +const cursor = StateTrooper.patrolRunLoop(config, (cursor) => { + // Re-render the component when state changes generate new cursors + React.render(, document.querySelector('body')); }); + +// Render the component with the initial cursor +React.render( + , + document.querySelector('body') +); + ``` Using cursors inside of the components diff --git a/package.json b/package.json index 00e8db9..48cd005 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "state-trooper", - "version": "2.0.0", + "version": "2.1.0", "description": "Application State Manager", "main": "index.js", "types": "index.d.ts", @@ -19,7 +19,7 @@ "homepage": "https://github.com/swipely/state-trooper", "dependencies": { "immutability-helper": "^2.2.0", - "js-csp": "^0.5.0", + "js-csp": "0.9.2", "lodash.isequal": "^4.5.0" }, "devDependencies": { diff --git a/src/run_update_loop.js b/src/run_update_loop.js new file mode 100644 index 0000000..e76bb98 --- /dev/null +++ b/src/run_update_loop.js @@ -0,0 +1,20 @@ +import { go, poll, take } from 'js-csp'; + +function runUpdateLoop(cursorCh, handleUpdate) { + const initialCursor = poll(cursorCh); + + go(function* () { + let cursor; + + while ((cursor = yield take(cursorCh))) { + // Allow the callback to exit the while loop + if (handleUpdate(cursor) === false) { + return; + } + } + }); + + return initialCursor; +} + +export default runUpdateLoop; diff --git a/src/state_trooper.js b/src/state_trooper.js index 15c4b50..129948e 100644 --- a/src/state_trooper.js +++ b/src/state_trooper.js @@ -1,10 +1,17 @@ import getStateByPath from './get_state_by_path'; import patrol from './patrol'; import { stakeoutAt } from './stakeout'; +import runUpdateLoop from './run_update_loop'; + +function patrolRunLoop(config, updateHandler) { + // Start an update loop using the channel created by patrol() + return runUpdateLoop(patrol(config), updateHandler); +} const StateTrooper = { getStateByPath: getStateByPath, patrol: patrol, + patrolRunLoop: patrolRunLoop, stakeout: stakeoutAt }; diff --git a/test/state_trooper_test.js b/test/state_trooper_test.js index e844ce5..92dade2 100644 --- a/test/state_trooper_test.js +++ b/test/state_trooper_test.js @@ -36,7 +36,7 @@ describe('StateTrooper', function () { it('puts a cursor on the cursor chan', function () { go(function* () { let cursor = yield take(cursorChan); - expect( cursor.derefJS() ).to.eql({ foo: 'bar' }); + expect( cursor.deref() ).to.eql({ foo: 'bar' }); }); }); @@ -68,6 +68,26 @@ describe('StateTrooper', function () { }); }); + describe('.patrolRunLoop', function () { + let config = { + state: { + foo: 'bar' + }, + dataStore: { + 'foo': { + fetcher: sinon.spy(), + persister: sinon.spy() + } + } + }; + + it('returns the initial cursor', function () { + let cursor = StateTrooper.patrolRunLoop(config, (cursor) => {}); + expect( cursor.deref() ).to.eql({ foo: 'bar' }); + expect( config.dataStore['foo'].fetcher.calledOnce ).to.be(true); + }); + }); + describe('.stakeout', function () { let cursorChan;