From 62a92035c37ba34366c9bf4527c3d2544908e7d5 Mon Sep 17 00:00:00 2001 From: Emrah Bilbay Date: Fri, 30 Nov 2018 21:40:37 +0300 Subject: [PATCH 1/3] add mutate queue and test it --- __tests__/index.spec.js | 39 +++++++++++++++++++++++++++++++++++++++ src/index.js | 25 ++++++++++++++++--------- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/__tests__/index.spec.js b/__tests__/index.spec.js index 426ae42..06ab8f8 100644 --- a/__tests__/index.spec.js +++ b/__tests__/index.spec.js @@ -443,4 +443,43 @@ describe("copy-on-write-store", () => { render(); expect(log).toEqual(["foo"]); }); + + + it("supports mutate calls before provider mounts", () => { + let log = []; + const { Provider, Consumer } = createState({ foo: "" }); + + let waitForMount = new Promise(resolve => { + class MutatingComponent extends React.Component { + componentDidMount() { + mutate(draft => { + draft.foo = "bar"; + resolve(); + }); + } + + render() { + return null; + } + } + + const App = () => ( + + state.foo]} + render={foo => { + log.push(foo); + return null; + }} + /> + + + ); + render(); + }); + + waitForMount.then(() => { + expect(log).toEqual(["bar"]); + }); + }); }); diff --git a/src/index.js b/src/index.js index 0f01346..55a5039 100644 --- a/src/index.js +++ b/src/index.js @@ -15,7 +15,7 @@ import React, { Component } from "react"; import produce from "immer"; import invariant from "invariant"; import shallowEqual from "fbjs/lib/shallowEqual"; -import createContext from 'create-react-context' +import createContext from "create-react-context"; // The default selector is the identity function function identityFn(n) { @@ -24,18 +24,17 @@ function identityFn(n) { export default function createCopyOnWriteState(baseState) { let updateState = null; + let mutateQueue = []; const State = createContext(baseState); // Wraps immer's produce. Only notifies the Provider // if the returned draft has been changed. function mutate(fn) { - invariant( - 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 mounted.` - ); - updateState(fn); + // If provider doesn't mounted yet, enqueue requests + if (updateState === null) { + mutateQueue.unshift(fn); + } else { + updateState(fn); + } } /** @@ -59,7 +58,15 @@ export default function createCopyOnWriteState(baseState) { `CopyOnWriteStoreProvider(...): There can only be a single ` + `instance of a provider rendered at any given time.` ); + updateState = this.updateState; + + // dequeue and call requests that pushed the queue before + // provider mounted + while (mutateQueue.length > 0) { + const fn = mutateQueue.pop(); + updateState(fn); + } } componentWillUnmount() { From ad8d1cc8ac1771a86437eab2a324ef6820c6ed21 Mon Sep 17 00:00:00 2001 From: Emrah Bilbay Date: Tue, 11 Dec 2018 05:10:42 +0300 Subject: [PATCH 2/3] more effective enqueue --- src/index.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/index.js b/src/index.js index 55a5039..8f10504 100644 --- a/src/index.js +++ b/src/index.js @@ -29,12 +29,7 @@ export default function createCopyOnWriteState(baseState) { // Wraps immer's produce. Only notifies the Provider // if the returned draft has been changed. function mutate(fn) { - // If provider doesn't mounted yet, enqueue requests - if (updateState === null) { - mutateQueue.unshift(fn); - } else { - updateState(fn); - } + updateState(fn); } /** @@ -50,15 +45,25 @@ export default function createCopyOnWriteState(baseState) { } class CopyOnWriteStoreProvider extends React.Component { + constructor(props) { + super(props); + + // If provider doesn't mounted yet, enqueue requests + updateState = mutateQueue.unshift; + this.mounted = false; + } + state = this.props.initialState || baseState; componentDidMount() { invariant( - updateState === null, + this.mounted === false, `CopyOnWriteStoreProvider(...): There can only be a single ` + `instance of a provider rendered at any given time.` ); + this.mounted = true; + updateState = this.updateState; // dequeue and call requests that pushed the queue before From cf6354f2db62befbad501449b64a7c3af8c167f8 Mon Sep 17 00:00:00 2001 From: Emrah Bilbay Date: Tue, 11 Dec 2018 05:17:39 +0300 Subject: [PATCH 3/3] add invariant / prettify code --- __tests__/index-flowtest.js | 16 ++++++++-------- __tests__/index.spec.js | 1 - src/index.js | 8 +++++++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/__tests__/index-flowtest.js b/__tests__/index-flowtest.js index 5d162f7..240e910 100644 --- a/__tests__/index-flowtest.js +++ b/__tests__/index-flowtest.js @@ -3,21 +3,21 @@ * @format */ -import React from 'react'; -import type { Store } from '../src/index.js'; -import createStore from '../src/index.js'; +import React from "react"; +import type { Store } from "../src/index.js"; +import createStore from "../src/index.js"; type User = { name: string, age: number }; type State = { users: Array, - version: number, + version: number }; const store: Store = createStore( ({ users: [], - version: 100, - }: State), + version: 100 + }: State) ); const { Provider, Consumer, mutate } = store; @@ -79,7 +79,7 @@ function testIncorrectConsumer() { function testMutate() { mutate((draft, state) => { - draft.users = [{ name: 'shengmin', age: 20 }]; + draft.users = [{ name: "shengmin", age: 20 }]; draft.version = state.version + 1; }); } @@ -87,7 +87,7 @@ function testMutate() { function testIncorrectMutate() { mutate((draft, state) => { // $FlowExpectedError - draft.users = ''; + draft.users = ""; // $FlowExpectedError draft.version = state.versions + 1; }); diff --git a/__tests__/index.spec.js b/__tests__/index.spec.js index 06ab8f8..3932632 100644 --- a/__tests__/index.spec.js +++ b/__tests__/index.spec.js @@ -444,7 +444,6 @@ describe("copy-on-write-store", () => { expect(log).toEqual(["foo"]); }); - it("supports mutate calls before provider mounts", () => { let log = []; const { Provider, Consumer } = createState({ foo: "" }); diff --git a/src/index.js b/src/index.js index 8f10504..6092c42 100644 --- a/src/index.js +++ b/src/index.js @@ -29,6 +29,12 @@ export default function createCopyOnWriteState(baseState) { // Wraps immer's produce. Only notifies the Provider // if the returned draft has been changed. function mutate(fn) { + invariant( + updateState !== null, + `mutate(...): you cannot call mutate when no CopyOnWriteStoreProvider ` + + `instance is constructed. Make sure to wrap your consumer components with ` + + `the returned Provider` + ); updateState(fn); } @@ -63,7 +69,7 @@ export default function createCopyOnWriteState(baseState) { ); this.mounted = true; - + updateState = this.updateState; // dequeue and call requests that pushed the queue before