From 53925495e486cf32eaa78939194d5fcf8f39d800 Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Sun, 24 Jun 2018 20:33:53 -0700 Subject: [PATCH] Always update state using setState updater function --- __tests__/index.spec.js | 34 ++++++++++++++++++++++++++++++++++ src/index.js | 39 +++++++++++++++------------------------ 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/__tests__/index.spec.js b/__tests__/index.spec.js index 7f9e210..21c79cb 100644 --- a/__tests__/index.spec.js +++ b/__tests__/index.spec.js @@ -71,6 +71,40 @@ describe("copy-on-write-store", () => { expect(log[0].posts).toEqual(log[1].posts); }); + it("doesnt update state if no change was made", () => { + let log = []; + class App extends React.Component { + render() { + return ( + +
+ + {state => { + log.push(state); + return null; + }} + +
+
+ ); + } + } + render(); + // First render is the base state + expect(log).toEqual([baseState]); + log = []; + mutate(draft => { + // Noop, no update made + }); + // No update should be processed + expect(log).toEqual([]); + mutate(draft => { + // Update to the current value, no update should be processed + draft.loggedIn = true; + }); + expect(log).toEqual([]); + }); + it("memoizes selectors", () => { let log = []; let updater; diff --git a/src/index.js b/src/index.js index 3e43aac..d8a605c 100644 --- a/src/index.js +++ b/src/index.js @@ -22,29 +22,19 @@ function identityFn(n) { } export default function createCopyOnWriteState(baseState) { - /** - * 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 = baseState; - let providerListener = null; + let updateState = null; const State = React.createContext(baseState); // Wraps immer's produce. Only notifies the Provider // if the returned draft has been changed. function mutate(fn) { invariant( - providerListener !== null, + updateState !== null, `mutate(...): you cannot call mutate when no CopyOnWriteStoreProvider ` + `instance is mounted. Make sure to wrap your consumer components with ` + `the returned Provider, and/or delay your mutate calls until the component ` + `tree is moutned.` ); - const nextState = produce(currentState, draft => fn(draft, currentState)); - if (nextState !== currentState) { - currentState = nextState; - providerListener(); - } + updateState(fn); } /** @@ -60,28 +50,29 @@ export default function createCopyOnWriteState(baseState) { } class CopyOnWriteStoreProvider extends React.Component { - state = this.props.initialState || currentState; + state = this.props.initialState || baseState; componentDidMount() { invariant( - providerListener === null, + updateState === null, `CopyOnWriteStoreProvider(...): There can only be a single ` + `instance of a provider rendered at any given time.` ); - providerListener = this.updateState; - // Allow a Provider to initialize state from props - if (this.props.initialState) { - currentState = this.props.initialState; - } + updateState = this.updateState; } componentWillUnmount() { - providerListener = null; - currentState = baseState; + updateState = null; } - updateState = () => { - this.setState(currentState); + updateState = fn => { + this.setState(state => { + const nextState = produce(state, draft => fn(draft, state)); + if (nextState === state) { + return null; + } + return nextState; + }); }; render() {