diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a38333e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +notifications: + email: false +node_js: 'node' \ No newline at end of file diff --git a/README.md b/README.md index 182bdcc..ff31fe5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ An immutable React state management library with a simple mutable API, memoized [Check out this small demo.](https://codesandbox.io/s/yp34vpk50j) +[![Build Status](https://travis-ci.org/aweary/react-copy-write.svg?branch=master)](https://travis-ci.org/aweary/react-copy-write) +[![npm](https://img.shields.io/npm/v/react-copy-write.svg)](https://www.npmjs.com/package/react-copy-write) +[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/aweary/react-copy-write/blob/master/LICENSE) + + + ## Overview @@ -28,7 +34,7 @@ react-copy-write lets you use straightforward mutations to update an immutable s * [Composing Selectors](#composing-selectors) * [Applying Multiple Selectors](#applying-multiple-selectors) * [Updating State](#updating-state) - * [`createUpdater`](#createupdater) + * [`createMutator`](#createmutator) ## Installation @@ -255,7 +261,7 @@ const SearchBar = () => ( state.search}> {(search, mutate) => ( mutate(draft => { // Update draft.search (which will end up being state.search) via mutation @@ -289,7 +295,7 @@ const SearchBar = () => (
state.search}> {(search) => ( - + )}
diff --git a/src/index.js b/src/index.js index 4234ab8..1d99b0d 100644 --- a/src/index.js +++ b/src/index.js @@ -45,16 +45,52 @@ function identityFn(n: T): T { return n; } -export default function createCopyOnWriteState(baseState: T) { +function Store(baseState: T) { + let state: T = baseState; + function getState(): T { + return state; + } + function setState(newState: T) { + state = newState; + } + return { + getState, + setState + }; +} + +export default function createCopyOnWriteState( + baseState: T, + middlewares = [] +) { /** * The current state is stored in a closure, shared by the consumers and * the provider. Consumers still respect the Provider/Consumer contract * that React context enforces, by only accessing state in the consumer. */ let currentState: T = baseState; + let store = Store(baseState); let providerListener = null; // $FlowFixMe React.createContext exists now const State = React.createContext(baseState); + + const updateMiddleWare = getState => next => fn => { + const state = getState(); + const nextState = next(state, fn); + if (nextState !== state) { + store.setState(nextState); + providerListener(); + } + }; + + let chainedProduce = produce; + middlewares = middlewares.concat(updateMiddleWare); + middlewares = middlewares.slice(); + middlewares.reverse(); + middlewares.forEach( + middleware => (chainedProduce = middleware(store.getState)(chainedProduce)) + ); + // Wraps immer's produce. Only notifies the Provider // if the returned draft has been changed. function update(fn: UpdateFn) { @@ -63,13 +99,9 @@ export default function createCopyOnWriteState(baseState: T) { `update(...): you cannot call update when no CopyOnWriteStoreProvider ` + `instance is mounted. Make sure to wrap your consumer components with ` + `the returned Provider, and/or delay your update calls until the component ` + - `tree is moutned.` + `tree is mounted.` ); - const nextState = produce(currentState, fn); - if (nextState !== currentState) { - currentState = nextState; - providerListener(); - } + chainedProduce(fn); } /** @@ -77,12 +109,12 @@ export default function createCopyOnWriteState(baseState: T) { * to calling mutate(...) directly, except you can define it statically, * and have any additional arguments forwarded. */ - function createMutator(fn: UpdateFn) { + function createMutator(fn: UpdateFn) { return (...args: mixed[]) => { update(draft => { fn(draft, ...args); - }) - } + }); + }; } class CopyOnWriteStoreProvider extends React.Component< @@ -105,7 +137,7 @@ export default function createCopyOnWriteState(baseState: T) { } updateState = () => { - this.setState(currentState); + this.setState(store.getState()); }; render() { @@ -187,6 +219,7 @@ export default function createCopyOnWriteState(baseState: T) { Consumer: CopyOnWriteConsumer, Mutator: CopyOnWriteMutator, update, - createMutator + createMutator, + getState: store.getState }; }