Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to take in middleware #18

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: node_js
notifications:
email: false
node_js: 'node'
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)



</div>

## Overview
Expand All @@ -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

Expand Down Expand Up @@ -255,7 +261,7 @@ const SearchBar = () => (
<State.Consumer selector={state => state.search}>
{(search, mutate) => (
<input
value={state}
value={search}
onChange={event =>
mutate(draft => {
// Update draft.search (which will end up being state.search) via mutation
Expand Down Expand Up @@ -289,7 +295,7 @@ const SearchBar = () => (
<div className="search-bar">
<State.Consumer selector={state => state.search}>
{(search) => (
<input value={state} onChange={setSearch} />
<input value={search} onChange={setSearch} />
)}
</State.Consumer>
</div>
Expand Down
57 changes: 45 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,52 @@ function identityFn<T>(n: T): T {
return n;
}

export default function createCopyOnWriteState<T>(baseState: T) {
function Store<T>(baseState: T) {
let state: T = baseState;
function getState(): T {
return state;
}
function setState(newState: T) {
state = newState;
}
return {
getState,
setState
};
}

export default function createCopyOnWriteState<T>(
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<T>) {
Expand All @@ -63,26 +99,22 @@ export default function createCopyOnWriteState<T>(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);
}

/**
* createMutator lets you create a mutator function that is similar
* to calling mutate(...) directly, except you can define it statically,
* and have any additional arguments forwarded.
*/
function createMutator(fn: UpdateFn<T>) {
function createMutator(fn: UpdateFn<T>) {
return (...args: mixed[]) => {
update(draft => {
fn(draft, ...args);
})
}
});
};
}

class CopyOnWriteStoreProvider extends React.Component<
Expand All @@ -105,7 +137,7 @@ export default function createCopyOnWriteState<T>(baseState: T) {
}

updateState = () => {
this.setState(currentState);
this.setState(store.getState());
};

render() {
Expand Down Expand Up @@ -187,6 +219,7 @@ export default function createCopyOnWriteState<T>(baseState: T) {
Consumer: CopyOnWriteConsumer,
Mutator: CopyOnWriteMutator,
update,
createMutator
createMutator,
getState: store.getState
};
}