Repository: reduxjs/react-redux
Pull request #1000 (2018-08-14 17:21:23)

Use React.createContext() by @cellog
**Supersedes #997, based off of master so no merge conflicts**

**takes into account @timdorr 's request regarding Provider.spec.js not using canary values**
**to see the commit history that led to this, check out #997.**

This fixes
#914 (implements forwardRef and works with forwardRef element as connected component too)
#943 (removes prop-types in production build)
#802 (no try/catch block - mSP is called directly from render() and is bubbled up naturally)

and addresses
#950 (implements React context)
#890 (uses async-safe subscription to redux in Provider)

This PR combines an alternate approach to implementing Context to #995 and also the move to react-testing-library (#996) because testing React context with enzyme isn't yet possible. The PR does the following:

* implements context and subscriptions via React's `React.createContext()`
* adds support for refs directly via `React.forwardRef`
* allows multiple children in the Provider component
* allows changing the store prop in Provider (for hot reloading, or switching out for a new store)
* allow nested Providers, the lowest provider in the tree controls children below it (feature of React context, not react-redux)
* replaces passing store as prop by passing custom context provider to `<Provider>` and context consumer to connected components:

```js
function Comp(props) {
  return <div>{props.hi}</div>
}

const Container = connect(...)(Comp)

const customContext = React.createContext()

function FancyPants() {
  return (
    <Provider store={store1}>
      Primary provider
      <Provider store={store2} context={customContext.Provider}>
        <Container>
          Uses store 1
          <Container>
            Also uses store 1
            <Container consumer={customContext.Consumer}>
              Uses store 2
              <Container /> <!-- this uses store 1 again -->
            </Container>
          </Container
        </Container>
      </Provider>
    </Provider>
  )
}
```
* it reduces code size by almost 25% (drops 80 lines)
* removing subscriptions greatly reduces maintenance complexity
* no usage of `getDerivedStateFromProps`, instead, derived props are memoized in `render()`
* more async-ready because all subscribing is done in `componentDidMount` and `componentDidUpdate` of the `Provider` component, subscriptions are not handled at all in connected components, except to read from React's context consumer. This doesn't address tearing and other async issues that are caused by redux being synchronous in nature. Fixing this will require breaking redux's backwards compatibility, and that's a different discussion.
* probably more hot loader compatible, again because subscriptions are isolated in `Provider` and handled by React context.
* Tests: removes testing in React prior to 16.4, ups dependencies to React 16.4+, adds react-testing-library dependency, removing enzyme and react-test-renderer deps. It preserves the multi-version testing framework, in order to add new React versions for testing as they are released. Several tests are updated to support the backwards-compatibility breaks

Drawbacks to this approach:
* BC break: `withRef` is gone for connected components. The code errors out explaining that the user should simply pass a ref in and it will work. This will cause codebases to break that are relying on withRef. Fortunately, the fix is to remove withRef and update code to use standard reference handling procedures in React. docs need to be updated
* BC break: `store` cannot be passed as a prop to connected components. The code errors if a user attempts to pass store to a connected component. The error explains to use a custom `Provider` (as noted above) to fix this, but will cause breaks in the few programs using this feature. docs need to be updated
* BC break: `storeKey` is irrelevant in `connect()` options. The code errors if a user attempts to pass this option in `connect()`'s options. The error explains to simply use a nested `Provider` or a custom `Provider`. docs need to be updated
* requires 3 wrapper components, `Connect`, the Context consumer, and `PureComponent` wrapper around the component in pure mode (2 in impure mode). This is what makes the simplicity possible. The top-most component retrieves the context containing redux state, calculates derived props, and passes them to the inner component. Rather than try to do all of this before `shouldComponentUpdate`, which leads to tons of complexity and brittle code, we pass it to the underlying component. sCU in `PureComponent` works perfectly for our needs, preventing update if the derivedProps are unchanged.
* in order to support forwardRef, we have to import `react-is`, which does not yet have an esm build, so we gain a little heft in exchange for the reduction in code
* the existing hot loading tests don't work at all, and had to be stripped. The implementation assumes that when hot loading, we may get passed a new store to Provider, which will require unsubscribing from the old and subscribing to the new in componentDidUpdate, but this is not tested in the real world yet. There are tests for this new behavior in `Provider.spec.js`
* because tests didn't work at all, the diff is huge.

@markerikson @timdorr 

markerikson: I just ran another perf benchmark for 5.0.7, #995, and #1000 , set to 2500 connected components.  Here's the FPS (avg / min-max) and Chrome trace time split results for each one (30-second test runs):

|       | FPS           | Scripting | Painting | Rendering |
|-------|---------------|-----------|----------|-----------|
| 5.0.7 | 28.26 (24-30) | 17326     | 9569     | 2072      |
| #995  | 24.26 (19-28) | 19067     | 8246     | 1767      |
| #1000 | 15.8 (14-17)  | 22401     | 5589     | 1187      |


The implementation of #1000 is conceptually interesting, and I like its relative simplicity (just memoize in render vs splitting across two components), but based on these numbers it seems noticeably slower.

Can anyone else confirm the numbers?  I did fresh builds from both PR branches (including tweaking the Rollup config to set `output.sourcemap : true`, and updating the perf script to copy the sourcemaps to the app build folder to get some better names in the traces).
cellog: can you link to the perf script/setup you're using again?
cellog: nvm found it here https://github.com/markerikson/benchmark-react-redux-perf
cellog: needs a pushed commit with your changes though @markerikson 
markerikson: I'm going to try updating #995 to add in `forwardRef` and stuff so that it's more equivalent to #1000 , then try re-running the comparison.
markerikson: I pushed the sourcemap usage changes to the perf repo.  For building React-Redux, it's just adding `sourcemap : true` to the `output {}` object in `rollup.config.js`.

I'll throw the average calculation into the perf runner.  It's probably not entirely correct, as the FPS value changes could be happening at different times, and so the "average" should really be calculated using frame timestamps or something, but it's at least _an_ objective comparison.
timdorr: The current branch is now published to npm under the `next-1000` tag: https://www.npmjs.com/package/react-redux?activeTab=versions
timdorr: Oh, and renamed this and #995 so they pair up well in the PR list and browser's tab bar.
markerikson: I just pushed an update to this branch to use `{forwardRef : true}` instead of `{withRef : "forwardRef"}`, for consistency with #995 .  The `next-1000` tag has been republished (thanks, Tim!).
markerikson: Hmm. I'd guess a lot of it had to do with not being able to propagate a reference change in `context.store` using legacy context.  In other words, if the `<Provider>` supplied a new store in its context, nested components would never see the new reference because of intervening `shouldComponentUpdate` usages blocking the change (a known flaw in how legacy context worked).

Not only does `createContext` not have that update problem, but we're sticking the store _state_ into context now (and, actually, the store itself too, although we're just grabbing `store.dispatch`).  I think this branch has support for swapping the store?  Don't remember if I added it to the #995 branch.
vladshcherbin: Since react-router v4.4 is coming with new context, there is [a bug related to react-redux v5](https://github.com/ReactTraining/react-router/issues/6424).

Updating to this pull request fixed it. 🎉 
stevemao: @vladshcherbin is the bug still in v5.1.0?
vladshcherbin: @stevemao yep
netlify[bot]: Deploy preview for *react-redux-docs* ready!

Built with commit 329f3a5d0aa6b2e0f351f7b911006eafd110f888

https://deploy-preview-1000--react-redux-docs.netlify.com
netlify[bot]: Deploy preview for *react-redux-docs* ready!

Built with commit 24af213f2b3266d307f3c3ea78d1754afcaa42bf

https://deploy-preview-1000--react-redux-docs.netlify.com
timdorr: Rebased on master and any conflicts fixed up. Also dealt with some package-lock.json noise I made. Whoops!
markerikson: Gimme a chance to look things over tomorrow - just got back from the conference.
markerikson: Reviewing the differences between this and 995 right now.

First question: since we're already forcing a minimum of 16.3 anyway, should we just go straight to 16.6 instead?  I see a couple places where we're using `PureComponent`, and we could replace that with `React.memo()` instead.
Kovensky: There actually is a really small snag that I encountered with using `React.memo` and the other 16.6 goodies: they'll fail `propTypes` validation for components that expect to receive a component as a prop.

They're not functions, they're special objects ("exotic components" as I'm calling them) so they'll fail `PropTypes.func`.

At worst all that this does is print an angry warning in the console, though.
markerikson: @Kovensky : hmm.  Interesting point.  Very similar to what we already faced over in #914 / #1062 .  

I'm inclined to say we go ahead with using `React.memo()` anyway.  It's a major version bump, so we've got an excuse, and the ecosystem is going to need to catch up with this being a thing soon anyway.
markerikson: Suppose if I'm going to review it, I might as well make it an actual review to get my thoughts out there.  Loading...
Kovensky: ```ts
// The PropTypes validator in the Route component does not accept `React.lazy` components
// because their `typeof` is not `'function'` (it's an object handled specially by react fiber).
if (__DEV__ && (Route as React.ComponentType<any>).propTypes !== undefined) {
  delete (Route as React.ComponentType<any>).propTypes!.component
}
```

😇
markerikson: Summarizing the review:

- ^16.6
- We should allow passing an _entire_ context object for customization
- Warning messages should be dev-only
- `PureComponent` should be replaced with `React.memo()`
- Remove mentions of `observedBits`
- Simplify/remove `forwardRef`-specific function declarations
- Probably simplify use of `propTypes`
markerikson: @cellog told me a couple days ago he's busy with some interviews atm, so I'll tackle this.  I may have a little time to play with this Wednesday evening.  Otherwise, I should be able to put most of my attention to it over this next weekend.  I'll aim to have the PR cleaned up by then.
cellog: > @cellog told me a couple days ago he's busy with some interviews atm, so I'll tackle this. I may have a little time to play with this Wednesday evening. Otherwise, I should be able to put most of my attention to it over this next weekend. I'll aim to have the PR cleaned up by then.

Thank you! I am swamped, and appreciate the effort.
markerikson: @cellog : Sure!  I obviously don't want to take away any of the credit for working on this.  Sure, it's a collaborative effort, but you've done a lot of work here.  That said, you're busy, I've got tweaks I'd like made, and so I'll go ahead and make them over the next couple days so we can move this forward.
cellog: I love this, because open source ™ :) keep up the great work. I'll surface for air soonish! 
markerikson: Hmm.  So I'm trying to work on this, but the 16.6 test suite won't run.  Jest fails with this error:

`The --config option requires a JSON string literal, or a file path with a .js or .json extension.`

It sure _looks_ like there's a valid JSON string literal being generated and passed as an argument.  No idea what's going on here.

Fortunately, I can run the tests "manually" from WebStorm, but this seems busted.
markerikson: I'm looking at this again today, and I'm semi-stuck on the handling of `forwardRef` vs `wrapperProps`.

The current commit has totally alternate code paths for rendering the child component with and without a forward ref.  There's near-duplicate `renderWrappedComponent` and `renderWrappedComponentWithRef` functions, and the logic does:

https://github.com/reduxjs/react-redux/blob/e742caea859d22104e5d40fbf5f3c4ef6b8995dc/src/components/connectAdvanced.js#L234-L235

There's also alternate versions of a `PureWrapper` component for with and without passing down `forwardRef`.

I understand _why_ that's there.  It's because we want to maintain referential integrity of the `wrapperProps` object so that it can be easily checked for changes via a single `===` comparison in that memoization function:

https://github.com/reduxjs/react-redux/blob/e742caea859d22104e5d40fbf5f3c4ef6b8995dc/src/components/connectAdvanced.js#L164-L167

But, the code duplication really bothers me.  And, 99.8% of the time people are going to be using standard `connect`, not `connectAdvanced`, and isn't that what the existing `mapState`-ish selector logic ultimately does anyway?

On the other hand, I could see that if we just went ahead and did `<Connect {...wrapperProps} forwardRef={ref} />` in the forwarding function, and the ref was a callback ref, we could wind up with the combined "wrapper props" actually considered changed on every render.  _If_ `mapState` depends on `ownProps`, we'd be re-running that more often that actually necessary.

Any thoughts?
markerikson: @cellog : what was the actual performance difference that you saw there?
stevemao: I'm wondering if https://github.com/facebook/react/issues/13739 is a blocker for perf. @alexreardon @gaearon thoughts?
markerikson: I don't think it's an actual _blocker_, although yes, anything the React team can do to speed up context updates would be helpful.  What I will say is that I want to start getting people to try this out in actual apps so that we can get feedback on real-world performance, not just the artificial stress-test benchmarks we've been trying to run.
alexreardon: My use case is fairy extreme as I perform 60 updates per second. There is a
slow down. I can get some numbers on Monday if you like
On Sat, 3 Nov 2018 at 12:52 pm, Mark Erikson <notifications@github.com>
wrote:

> I don't think it's an actual *blocker*, although yes, anything the React
> team can do to speed up context updates would be helpful. What I will say
> is that I want to start getting people to try this out in actual apps so
> that we can get feedback on real-world performance, not just the artificial
> stress-test benchmarks we've been trying to run.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/reduxjs/react-redux/pull/1000#issuecomment-435551759>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ACFN7b7TtfHPhhq8eHdXiaeY1HkzNKTmks5urPbugaJpZM4V82GM>
> .
>

markerikson: I've been hacking on this tonight, and I've got something that at least passes the tests after a bunch of cleanup and restructuring.  About to test this against the benchmarks repo.
markerikson: ## Progress Update - Ready for Beta-ish?

I think I've got this branch not just cleaned up, but actually improved quite a bit.  In particular, I've managed to simplify the logic around `pure` and `forwardRef`, _and_ it looks like I've sped up this branch significantly compared to its earlier iterations.  It's still not as fast as 5.0.7, but it's the fastest of the v6 variants we've tried thus far.

**I think this is probably ready for a beta release of some kind so we can start getting real-world feedback.**

### Performance Techniques

The big improvement in these latest commits was due to bringing back a trick from v4: memoizing the actual element reference returned from `Connect.render()`.  It's not particularly documented, but if you return the _exact_ same element reference from a render method as the last time, React will bail out of rendering that child.  Since v4 did all of its real work in `render()`, the end looked like this:

```js
        if (!haveMergedPropsChanged && renderedElement) {
          return renderedElement
        }

        if (withRef) {
          this.renderedElement = createElement(WrappedComponent, {
            ...this.mergedProps,
            ref: 'wrappedInstance'
          })
        } else {
          this.renderedElement = createElement(WrappedComponent,
            this.mergedProps
          )
        }

        return this.renderedElement
```

In v5, that kind of went away, since we do all the memoization work up front, and only call `setState()` once we _know_ the wrapped component needs to re-render.

In v6, we're back to doing more work in `render` again thanks to the use of a `<Context.Consumer>`.  Our previous attempts at handling the "no changes" case involved stuff like `PureComponent` as an extra wrapper for the real wrapped component.

I remembered the memoized element trick while playing with the hooks-ified branch I posted over in #1065 , and was able to reuse that with a handwritten memoization function here.

### Performance Benchmarks

I've updated the benchmarks repo over at https://github.com/reduxjs/react-redux-benchmarks , and ran a new set of benchmarks with version 5.0.7, an earlier commit from this branch (4855c84, which I think was before Tim rebased it), and my new commit 2d06cdf .

The repo currently has three benchmark apps:

- `stockticker`: a flat tree of N stock ticker components, rapidly updating at random
- `tree-view`: a variation of the Redux `tree-view` example app, with N counter components in a seeded-randomized tree, and fake clicks on increment/delete/insert buttons at random
- `twitter-lite`: three lists, with new entries continually inserting into them at random

They're artificial stress tests, but they are at least some kind of objective numbers that we can look at.

Each test is run for 30 seconds twice in Puppeteer.  The first time tries to capture FPS results, the second activates a Chrome trace to capture scripting/rendering/painting times.

In theory, anyone ought to be able to clone the benchmarks repo, build from this branch, drop the UMD build into `$BENCHMARKS_REPO/react-redux-versions`, and roughly replicate these results.

And now, *drumroll please*, the results:

#### `stockticker`
```
+----------------------------------------------------------------
¦ Version          ¦ Avg FPS ¦ Scripting ¦ Rendering ¦ Painting ¦
+------------------+---------+-----------+-----------+----------+
¦ 5.0.7            ¦ 24.32   ¦ 10325.44  ¦ 14866.93  ¦ 3261.55  ¦
+------------------+---------+-----------+-----------+----------+
¦ 6.0-1000-2d06cdf ¦ 18.40   ¦ 14596.15  ¦ 11756.86  ¦ 2474.33  ¦
+------------------+---------+-----------+-----------+----------+
¦ 6.0-1000-4855c84 ¦ 15.63   ¦ 17865.38  ¦ 9266.14   ¦ 1906.47  ¦
+----------------------------------------------------------------
```

#### `tree-view`
```
+----------------------------------------------------------------
¦ Version          ¦ Avg FPS ¦ Scripting ¦ Rendering ¦ Painting ¦
+------------------+---------+-----------+-----------+----------+
¦ 5.0.7            ¦ 43.41   ¦ 9854.61   ¦ 10257.18  ¦ 704.06   ¦
+------------------+---------+-----------+-----------+----------+
¦ 6.0-1000-2d06cdf ¦ 42.37   ¦ 10476.60  ¦ 12690.35  ¦ 465.57   ¦
+------------------+---------+-----------+-----------+----------+
¦ 6.0-1000-4855c84 ¦ 41.78   ¦ 12019.21  ¦ 12074.83  ¦ 447.20   ¦
+----------------------------------------------------------------
```

#### `twitter-lite`
```
+-----------------------------------------------------------------
¦ Version          ¦ Avg FPS ¦ Scripting ¦ Rendering ¦ Painting ¦ 
+------------------+---------+-----------+-----------+----------+-
¦ 5.0.7            ¦ 40.24   ¦ 21362.26  ¦ 4813.54   ¦ 570.79   ¦ 
+------------------+---------+-----------+-----------+----------+-
¦ 6.0-1000-2d06cdf ¦ 31.90   ¦ 23361.35  ¦ 3659.78   ¦ 473.59   ¦ 
+------------------+---------+-----------+-----------+----------+-
¦ 6.0-1000-4855c84 ¦ 29.39   ¦ 24938.56  ¦ 2910.20   ¦ 419.44   ¦ 
+-----------------------------------------------------------------
```


### Summary

All of our v6 PRs have been considerably slower than v5 **in these artificial benchmarks**, and I don't think there's a lot we can do about that.  v5 could bail out before React ever got involved, but with v6, React is _always_ involved, and has to traverse the tree to find all of the potential context consumers.

However, this branch is measurably faster than our earlier attempts, and it's time we find out just how well it works in the real world.

Longer term, I'm hoping [the React team will find ways to speed up context updates](https://github.com/facebook/react/issues/13739), which would benefit us considerably.

@timdorr, let's get this published as `react-redux@next` and see what happens! :)
timdorr: OK, ran things through Prettier (I'll get on top of #1071 after this) and we should be good to go.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 46436af..e4df5ce 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,10 +3,6 @@ node_js:
   - "10"
 env:
   matrix:
-  - REACT=0.14
-  - REACT=15
-  - REACT=16.2
-  - REACT=16.3
   - REACT=16.4
   - REACT=16.5
   - REACT=16.6
diff --git a/package-lock.json b/package-lock.json
index eae14f1..f5f825a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1336,7 +1336,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -1345,9 +1345,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "js-tokens": {
@@ -2696,7 +2696,8 @@
     "asap": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
-      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+      "dev": true
     },
     "asn1": {
       "version": "0.2.3",
@@ -3927,6 +3928,7 @@
       "version": "0.1.12",
       "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
       "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+      "dev": true,
       "requires": {
         "iconv-lite": "~0.4.13"
       }
@@ -4374,7 +4376,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         }
       }
@@ -4463,6 +4465,7 @@
       "version": "0.8.16",
       "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
       "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
+      "dev": true,
       "requires": {
         "core-js": "^1.0.0",
         "isomorphic-fetch": "^2.1.1",
@@ -4476,7 +4479,8 @@
         "core-js": {
           "version": "1.2.7",
           "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
-          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
+          "dev": true
         }
       }
     },
@@ -5417,7 +5421,8 @@
     "iconv-lite": {
       "version": "0.4.18",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz",
-      "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA=="
+      "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==",
+      "dev": true
     },
     "ignore": {
       "version": "3.3.7",
@@ -5809,7 +5814,8 @@
     "is-stream": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
     },
     "is-symbol": {
       "version": "1.0.1",
@@ -5860,6 +5866,7 @@
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
       "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+      "dev": true,
       "requires": {
         "node-fetch": "^1.0.1",
         "whatwg-fetch": ">=0.10.0"
@@ -5944,7 +5951,7 @@
           "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
           "dev": true,
           "requires": {
-            "has-flag": "1.0.0"
+            "has-flag": "^1.0.0"
           }
         }
       }
@@ -6010,7 +6017,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -6019,9 +6026,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "jest-cli": {
@@ -6074,7 +6081,7 @@
           "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
           "dev": true,
           "requires": {
-            "ansi-regex": "3.0.0"
+            "ansi-regex": "^3.0.0"
           }
         },
         "supports-color": {
@@ -6131,7 +6138,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "babel-code-frame": {
@@ -6205,9 +6212,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "debug": {
@@ -6276,7 +6283,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -6285,9 +6292,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "pretty-format": {
@@ -6440,7 +6447,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -6449,9 +6456,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "pretty-format": {
@@ -6550,7 +6557,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -6559,9 +6566,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "pretty-format": {
@@ -6644,7 +6651,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -6653,9 +6660,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "pretty-format": {
@@ -6752,7 +6759,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -6761,9 +6768,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "supports-color": {
@@ -6861,7 +6868,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "babel-code-frame": {
@@ -6935,9 +6942,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "debug": {
@@ -7008,7 +7015,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -7017,9 +7024,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "pretty-format": {
@@ -7126,7 +7133,7 @@
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -7135,9 +7142,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.1",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "5.5.0"
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
           }
         },
         "pretty-format": {
@@ -7738,6 +7745,7 @@
       "version": "1.7.3",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
       "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+      "dev": true,
       "requires": {
         "encoding": "^0.1.11",
         "is-stream": "^1.0.1"
@@ -8245,6 +8253,7 @@
       "version": "7.3.1",
       "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
       "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+      "dev": true,
       "requires": {
         "asap": "~2.0.3"
       }
@@ -8260,11 +8269,10 @@
       }
     },
     "prop-types": {
-      "version": "15.6.1",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz",
-      "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==",
+      "version": "15.6.2",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
+      "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
       "requires": {
-        "fbjs": "^0.8.16",
         "loose-envify": "^1.3.1",
         "object-assign": "^4.1.1"
       }
@@ -8399,11 +8407,6 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz",
       "integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g=="
     },
-    "react-lifecycles-compat": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
-      "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
-    },
     "react-testing-library": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/react-testing-library/-/react-testing-library-5.0.0.tgz",
@@ -9365,7 +9368,8 @@
     "setimmediate": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+      "dev": true
     },
     "shebang-command": {
       "version": "1.2.0",
@@ -9839,11 +9843,11 @@
           "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
           "dev": true,
           "requires": {
-            "graceful-fs": "4.1.11",
-            "parse-json": "2.2.0",
-            "pify": "2.3.0",
-            "pinkie-promise": "2.0.1",
-            "strip-bom": "2.0.0"
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^2.2.0",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0",
+            "strip-bom": "^2.0.0"
           }
         },
         "path-type": {
@@ -9852,9 +9856,9 @@
           "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
           "dev": true,
           "requires": {
-            "graceful-fs": "4.1.11",
-            "pify": "2.3.0",
-            "pinkie-promise": "2.0.1"
+            "graceful-fs": "^4.1.2",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
           }
         },
         "read-pkg": {
@@ -9863,9 +9867,9 @@
           "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
           "dev": true,
           "requires": {
-            "load-json-file": "1.1.0",
-            "normalize-package-data": "2.4.0",
-            "path-type": "1.1.0"
+            "load-json-file": "^1.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^1.0.0"
           }
         },
         "read-pkg-up": {
@@ -9874,8 +9878,8 @@
           "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
           "dev": true,
           "requires": {
-            "find-up": "1.1.2",
-            "read-pkg": "1.1.0"
+            "find-up": "^1.0.0",
+            "read-pkg": "^1.0.0"
           }
         },
         "strip-bom": {
@@ -9884,7 +9888,7 @@
           "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
           "dev": true,
           "requires": {
-            "is-utf8": "0.2.1"
+            "is-utf8": "^0.2.0"
           }
         }
       }
@@ -10036,7 +10040,8 @@
     "ua-parser-js": {
       "version": "0.7.17",
       "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
-      "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g=="
+      "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==",
+      "dev": true
     },
     "uglify-js": {
       "version": "3.4.9",
@@ -10327,7 +10332,8 @@
     "whatwg-fetch": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
-      "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
+      "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=",
+      "dev": true
     },
     "whatwg-mimetype": {
       "version": "2.2.0",
diff --git a/package.json b/package.json
index ec759fd..fbd0d70 100644
--- a/package.json
+++ b/package.json
@@ -40,17 +40,16 @@
     "coverage": "codecov"
   },
   "peerDependencies": {
-    "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0",
+    "react": "^16.6.0-0",
     "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0"
   },
   "dependencies": {
     "@babel/runtime": "^7.1.2",
-    "hoist-non-react-statics": "^3.0.0",
+    "hoist-non-react-statics": "^3.0.1",
     "invariant": "^2.2.4",
     "loose-envify": "^1.1.0",
-    "prop-types": "^15.6.1",
-    "react-is": "^16.6.0",
-    "react-lifecycles-compat": "^3.0.0"
+    "prop-types": "^15.6.2",
+    "react-is": "^16.6.0"
   },
   "devDependencies": {
     "@babel/cli": "^7.1.2",
diff --git a/src/components/Context.js b/src/components/Context.js
new file mode 100644
index 0000000..d1169aa
--- /dev/null
+++ b/src/components/Context.js
@@ -0,0 +1,5 @@
+import React from 'react'
+
+export const ReactReduxContext = React.createContext(null)
+
+export default ReactReduxContext
diff --git a/src/components/Provider.js b/src/components/Provider.js
index f78e25e..03c5626 100644
--- a/src/components/Provider.js
+++ b/src/components/Provider.js
@@ -1,60 +1,84 @@
-import { Component, Children } from 'react'
+import React, { Component } from 'react'
 import PropTypes from 'prop-types'
-import { storeShape, subscriptionShape } from '../utils/PropTypes'
-import warning from '../utils/warning'
+import { ReactReduxContext } from './Context'
 
-let didWarnAboutReceivingStore = false
-function warnAboutReceivingStore() {
-  if (didWarnAboutReceivingStore) {
-    return
+class Provider extends Component {
+  constructor(props) {
+    super(props)
+
+    const { store } = props
+
+    this.state = {
+      storeState: store.getState(),
+      store
+    }
   }
-  didWarnAboutReceivingStore = true
-
-  warning(
-    '<Provider> does not support changing `store` on the fly. ' +
-    'It is most likely that you see this error because you updated to ' +
-    'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' +
-    'automatically. See https://github.com/reduxjs/react-redux/releases/' +
-    'tag/v2.0.0 for the migration instructions.'
-  )
-}
 
-export function createProvider(storeKey = 'store') {
-    const subscriptionKey = `${storeKey}Subscription`
+  componentDidMount() {
+    this._isMounted = true
+    this.subscribe()
+  }
 
-    class Provider extends Component {
-        getChildContext() {
-          return { [storeKey]: this[storeKey], [subscriptionKey]: null }
-        }
+  componentWillUnmount() {
+    if (this.unsubscribe) this.unsubscribe()
 
-        constructor(props, context) {
-          super(props, context)
-          this[storeKey] = props.store;
-        }
+    this._isMounted = false
+  }
 
-        render() {
-          return Children.only(this.props.children)
-        }
+  componentDidUpdate(prevProps) {
+    if (this.props.store !== prevProps.store) {
+      if (this.unsubscribe) this.unsubscribe()
+
+      this.subscribe()
     }
+  }
 
-    if (process.env.NODE_ENV !== 'production') {
-      Provider.prototype.componentWillReceiveProps = function (nextProps) {
-        if (this[storeKey] !== nextProps.store) {
-          warnAboutReceivingStore()
-        }
+  subscribe() {
+    const { store } = this.props
+
+    this.unsubscribe = store.subscribe(() => {
+      const newStoreState = store.getState()
+
+      if (!this._isMounted) {
+        return
       }
-    }
 
-    Provider.propTypes = {
-        store: storeShape.isRequired,
-        children: PropTypes.element.isRequired,
-    }
-    Provider.childContextTypes = {
-        [storeKey]: storeShape.isRequired,
-        [subscriptionKey]: subscriptionShape,
+      this.setState(providerState => {
+        // If the value is the same, skip the unnecessary state update.
+        if (providerState.storeState === newStoreState) {
+          return null
+        }
+
+        return { storeState: newStoreState }
+      })
+    })
+
+    // Actions might have been dispatched between render and mount - handle those
+    const postMountStoreState = store.getState()
+    if (postMountStoreState !== this.state.storeState) {
+      this.setState({ storeState: postMountStoreState })
     }
+  }
+
+  render() {
+    const Context = this.props.context || ReactReduxContext
+
+    return (
+      <Context.Provider value={this.state}>
+        {this.props.children}
+      </Context.Provider>
+    )
+  }
+}
 
-    return Provider
+Provider.propTypes = {
+  store: PropTypes.shape({
+    subscribe: PropTypes.func.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    getState: PropTypes.func.isRequired
+  }),
+  context: PropTypes.object,
+  children: PropTypes.any
 }
 
-export default createProvider()
+export default Provider
diff --git a/src/components/connectAdvanced.js b/src/components/connectAdvanced.js
index ca945d0..eec203d 100644
--- a/src/components/connectAdvanced.js
+++ b/src/components/connectAdvanced.js
@@ -1,34 +1,9 @@
 import hoistStatics from 'hoist-non-react-statics'
 import invariant from 'invariant'
-import { Component, createElement } from 'react'
+import React, { Component, PureComponent } from 'react'
 import { isValidElementType } from 'react-is'
 
-import Subscription from '../utils/Subscription'
-import { storeShape, subscriptionShape } from '../utils/PropTypes'
-
-let hotReloadingVersion = 0
-const dummyState = {}
-function noop() {}
-function makeSelectorStateful(sourceSelector, store) {
-  // wrap the selector in an object that tracks its results between runs.
-  const selector = {
-    run: function runComponentSelector(props) {
-      try {
-        const nextProps = sourceSelector(store.getState(), props)
-        if (nextProps !== selector.props || selector.error) {
-          selector.shouldComponentUpdate = true
-          selector.props = nextProps
-          selector.error = null
-        }
-      } catch (error) {
-        selector.shouldComponentUpdate = true
-        selector.error = error
-      }
-    }
-  }
-
-  return selector
-}
+import { ReactReduxContext } from './Context'
 
 export default function connectAdvanced(
   /*
@@ -59,33 +34,52 @@ export default function connectAdvanced(
     // probably overridden by wrapper functions such as connect()
     methodName = 'connectAdvanced',
 
-    // if defined, the name of the property passed to the wrapped element indicating the number of
+    // REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of
     // calls to render. useful for watching in react devtools for unnecessary re-renders.
     renderCountProp = undefined,
 
     // determines whether this HOC subscribes to store changes
     shouldHandleStateChanges = true,
 
-    // the key of props/context to get the store
+    // REMOVED: the key of props/context to get the store
     storeKey = 'store',
 
-    // if true, the wrapped element is exposed by this HOC via the getWrappedInstance() function.
+    // REMOVED: expose the wrapped component via refs
     withRef = false,
 
+    // use React's forwardRef to expose a ref of the wrapped component
+    forwardRef = false,
+
+    // the context consumer to use
+    context = ReactReduxContext,
+
     // additional options are passed through to the selectorFactory
     ...connectOptions
   } = {}
 ) {
-  const subscriptionKey = storeKey + 'Subscription'
-  const version = hotReloadingVersion++
-
-  const contextTypes = {
-    [storeKey]: storeShape,
-    [subscriptionKey]: subscriptionShape,
-  }
-  const childContextTypes = {
-    [subscriptionKey]: subscriptionShape,
-  }
+  invariant(
+    renderCountProp === undefined,
+    `renderCountProp is removed. render counting is built into the latest React dev tools profiling extension`
+  )
+
+  invariant(
+    !withRef,
+    'withRef is removed. To access the wrapped instance, use a ref on the connected component'
+  )
+
+  const customStoreWarningMessage =
+    'To use a custom Redux store for specific components,  create a custom React context with ' +
+    "React.createContext(), and pass the context object to React-Redux's Provider and specific components" +
+    ' like:  <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' +
+    'You may also pass a {context : MyContext} option to connect'
+
+  invariant(
+    storeKey === 'store',
+    'storeKey has been removed and does not do anything. ' +
+      customStoreWarningMessage
+  )
+
+  const Context = context
 
   return function wrapWithConnect(WrappedComponent) {
     if (process.env.NODE_ENV !== 'production') {
@@ -96,9 +90,8 @@ export default function connectAdvanced(
       );
     }
 
-    const wrappedComponentName = WrappedComponent.displayName
-      || WrappedComponent.name
-      || 'Component'
+    const wrappedComponentName =
+      WrappedComponent.displayName || WrappedComponent.name || 'Component'
 
     const displayName = getDisplayName(wrappedComponentName)
 
@@ -109,194 +102,139 @@ export default function connectAdvanced(
       renderCountProp,
       shouldHandleStateChanges,
       storeKey,
-      withRef,
       displayName,
       wrappedComponentName,
       WrappedComponent
     }
 
-    // TODO Actually fix our use of componentWillReceiveProps
-    /* eslint-disable react/no-deprecated */
+    const { pure } = connectOptions
 
-    class Connect extends Component {
-      constructor(props, context) {
-        super(props, context)
+    let OuterBaseComponent = Component
+    let FinalWrappedComponent = WrappedComponent
 
-        this.version = version
-        this.state = {}
-        this.renderCount = 0
-        this.store = props[storeKey] || context[storeKey]
-        this.propsMode = Boolean(props[storeKey])
-        this.setWrappedInstance = this.setWrappedInstance.bind(this)
+    if (pure) {
+      OuterBaseComponent = PureComponent
+    }
 
-        invariant(this.store,
-          `Could not find "${storeKey}" in either the context or props of ` +
-          `"${displayName}". Either wrap the root component in a <Provider>, ` +
-          `or explicitly pass "${storeKey}" as a prop to "${displayName}".`
-        )
+    function makeDerivedPropsSelector() {
+      let lastProps
+      let lastState
+      let lastDerivedProps
+      let lastStore
+      let sourceSelector
 
-        this.initSelector()
-        this.initSubscription()
-      }
+      return function selectDerivedProps(state, props, store) {
+        if (pure && lastProps === props && lastState === state) {
+          return lastDerivedProps
+        }
 
-      getChildContext() {
-        // If this component received store from props, its subscription should be transparent
-        // to any descendants receiving store+subscription from context; it passes along
-        // subscription passed to it. Otherwise, it shadows the parent subscription, which allows
-        // Connect to control ordering of notifications to flow top-down.
-        const subscription = this.propsMode ? null : this.subscription
-        return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
-      }
+        if (store !== lastStore) {
+          lastStore = store
+          sourceSelector = selectorFactory(
+            store.dispatch,
+            selectorFactoryOptions
+          )
+        }
 
-      componentDidMount() {
-        if (!shouldHandleStateChanges) return
-
-        // componentWillMount fires during server side rendering, but componentDidMount and
-        // componentWillUnmount do not. Because of this, trySubscribe happens during ...didMount.
-        // Otherwise, unsubscription would never take place during SSR, causing a memory leak.
-        // To handle the case where a child component may have triggered a state change by
-        // dispatching an action in its componentWillMount, we have to re-run the select and maybe
-        // re-render.
-        this.subscription.trySubscribe()
-        this.selector.run(this.props)
-        if (this.selector.shouldComponentUpdate) this.forceUpdate()
-      }
+        lastProps = props
+        lastState = state
 
-      componentWillReceiveProps(nextProps) {
-        this.selector.run(nextProps)
-      }
+        const nextProps = sourceSelector(state, props)
 
-      shouldComponentUpdate() {
-        return this.selector.shouldComponentUpdate
-      }
+        if (lastDerivedProps === nextProps) {
+          return lastDerivedProps
+        }
 
-      componentWillUnmount() {
-        if (this.subscription) this.subscription.tryUnsubscribe()
-        this.subscription = null
-        this.notifyNestedSubs = noop
-        this.store = null
-        this.selector.run = noop
-        this.selector.shouldComponentUpdate = false
+        lastDerivedProps = nextProps
+        return lastDerivedProps
       }
+    }
 
-      getWrappedInstance() {
-        invariant(withRef,
-          `To access the wrapped instance, you need to specify ` +
-          `{ withRef: true } in the options argument of the ${methodName}() call.`
-        )
-        return this.wrappedInstance
-      }
+    function makeChildElementSelector() {
+      let lastChildProps, lastForwardRef, lastChildElement
 
-      setWrappedInstance(ref) {
-        this.wrappedInstance = ref
-      }
+      return function selectChildElement(childProps, forwardRef) {
+        if (childProps !== lastChildProps || forwardRef !== lastForwardRef) {
+          lastChildProps = childProps
+          lastForwardRef = forwardRef
+          lastChildElement = (
+            <FinalWrappedComponent {...childProps} ref={forwardRef} />
+          )
+        }
 
-      initSelector() {
-        const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
-        this.selector = makeSelectorStateful(sourceSelector, this.store)
-        this.selector.run(this.props)
+        return lastChildElement
       }
+    }
 
-      initSubscription() {
-        if (!shouldHandleStateChanges) return
-
-        // parentSub's source should match where store came from: props vs. context. A component
-        // connected to the store via props shouldn't use subscription from context, or vice versa.
-        const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
-        this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
-
-        // `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
-        // the middle of the notification loop, where `this.subscription` will then be null. An
-        // extra null check every change can be avoided by copying the method onto `this` and then
-        // replacing it with a no-op on unmount. This can probably be avoided if Subscription's
-        // listeners logic is changed to not call listeners that have been unsubscribed in the
-        // middle of the notification loop.
-        this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
-      }
+    class Connect extends OuterBaseComponent {
+      constructor(props) {
+        super(props)
+        invariant(
+          forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey],
+          'Passing redux store in props has been removed and does not do anything. ' +
+            customStoreWarningMessage
+        )
+        this.selectDerivedProps = makeDerivedPropsSelector()
+        this.selectChildElement = makeChildElementSelector()
+        this.renderWrappedComponent = this.renderWrappedComponent.bind(this)
+      }
+
+      renderWrappedComponent(value) {
+        invariant(
+          value,
+          `Could not find "store" in the context of ` +
+            `"${displayName}". Either wrap the root component in a <Provider>, ` +
+            `or pass a custom React context provider to <Provider> and the corresponding ` +
+            `React context consumer to ${displayName} in connect options.`
+        )
+        const { storeState, store } = value
 
-      onStateChange() {
-        this.selector.run(this.props)
+        let wrapperProps = this.props
+        let forwardedRef
 
-        if (!this.selector.shouldComponentUpdate) {
-          this.notifyNestedSubs()
-        } else {
-          this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
-          this.setState(dummyState)
+        if (forwardRef) {
+          wrapperProps = this.props.wrapperProps
+          forwardedRef = this.props.forwardedRef
         }
-      }
 
-      notifyNestedSubsOnComponentDidUpdate() {
-        // `componentDidUpdate` is conditionally implemented when `onStateChange` determines it
-        // needs to notify nested subs. Once called, it unimplements itself until further state
-        // changes occur. Doing it this way vs having a permanent `componentDidUpdate` that does
-        // a boolean check every time avoids an extra method call most of the time, resulting
-        // in some perf boost.
-        this.componentDidUpdate = undefined
-        this.notifyNestedSubs()
-      }
+        let derivedProps = this.selectDerivedProps(
+          storeState,
+          wrapperProps,
+          store
+        )
 
-      isSubscribed() {
-        return Boolean(this.subscription) && this.subscription.isSubscribed()
-      }
+        if (pure) {
+          return this.selectChildElement(derivedProps, forwardedRef)
+        }
 
-      addExtraProps(props) {
-        if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
-        // make a shallow copy so that fields added don't leak to the original selector.
-        // this is especially important for 'ref' since that's a reference back to the component
-        // instance. a singleton memoized selector would then be holding a reference to the
-        // instance, preventing the instance from being garbage collected, and that would be bad
-        const withExtras = { ...props }
-        if (withRef) withExtras.ref = this.setWrappedInstance
-        if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
-        if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
-        return withExtras
+        return <FinalWrappedComponent {...derivedProps} ref={forwardedRef} />
       }
 
       render() {
-        const selector = this.selector
-        selector.shouldComponentUpdate = false
+        const ContextToUse = this.props.context || Context
 
-        if (selector.error) {
-          throw selector.error
-        } else {
-          return createElement(WrappedComponent, this.addExtraProps(selector.props))
-        }
+        return (
+          <ContextToUse.Consumer>
+            {this.renderWrappedComponent}
+          </ContextToUse.Consumer>
+        )
       }
     }
 
-    /* eslint-enable react/no-deprecated */
-
     Connect.WrappedComponent = WrappedComponent
     Connect.displayName = displayName
-    Connect.childContextTypes = childContextTypes
-    Connect.contextTypes = contextTypes
-    Connect.propTypes = contextTypes
 
-    if (process.env.NODE_ENV !== 'production') {
-      Connect.prototype.componentWillUpdate = function componentWillUpdate() {
-        // We are hot reloading!
-        if (this.version !== version) {
-          this.version = version
-          this.initSelector()
-
-          // If any connected descendants don't hot reload (and resubscribe in the process), their
-          // listeners will be lost when we unsubscribe. Unfortunately, by copying over all
-          // listeners, this does mean that the old versions of connected descendants will still be
-          // notified of state changes; however, their onStateChange function is a no-op so this
-          // isn't a huge deal.
-          let oldListeners = [];
-
-          if (this.subscription) {
-            oldListeners = this.subscription.listeners.get()
-            this.subscription.tryUnsubscribe()
-          }
-          this.initSubscription()
-          if (shouldHandleStateChanges) {
-            this.subscription.trySubscribe()
-            oldListeners.forEach(listener => this.subscription.listeners.subscribe(listener))
-          }
-        }
-      }
+    if (forwardRef) {
+      const forwarded = React.forwardRef(function forwardConnectRef(
+        props,
+        ref
+      ) {
+        return <Connect wrapperProps={props} forwardedRef={ref} />
+      })
+
+      forwarded.displayName = displayName
+      forwarded.WrappedComponent = WrappedComponent
+      return hoistStatics(forwarded, WrappedComponent)
     }
 
     return hoistStatics(Connect, WrappedComponent)
diff --git a/src/index.js b/src/index.js
index 2e0c092..22b1bd9 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,6 @@
-import Provider, { createProvider } from './components/Provider'
+import Provider from './components/Provider'
 import connectAdvanced from './components/connectAdvanced'
+import { ReactReduxContext } from './components/Context'
 import connect from './connect/connect'
 
-export { Provider, createProvider, connectAdvanced, connect }
+export { Provider, connectAdvanced, ReactReduxContext, connect }
diff --git a/src/utils/PropTypes.js b/src/utils/PropTypes.js
deleted file mode 100644
index 725b020..0000000
--- a/src/utils/PropTypes.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import PropTypes from 'prop-types'
-
-export const subscriptionShape = PropTypes.shape({
-  trySubscribe: PropTypes.func.isRequired,
-  tryUnsubscribe: PropTypes.func.isRequired,
-  notifyNestedSubs: PropTypes.func.isRequired,
-  isSubscribed: PropTypes.func.isRequired,
-})
-
-export const storeShape = PropTypes.shape({
-  subscribe: PropTypes.func.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  getState: PropTypes.func.isRequired
-})
diff --git a/src/utils/Subscription.js b/src/utils/Subscription.js
deleted file mode 100644
index db81467..0000000
--- a/src/utils/Subscription.js
+++ /dev/null
@@ -1,87 +0,0 @@
-// encapsulates the subscription logic for connecting a component to the redux store, as
-// well as nesting subscriptions of descendant components, so that we can ensure the
-// ancestor components re-render before descendants
-
-const CLEARED = null
-const nullListeners = { notify() {} }
-
-function createListenerCollection() {
-  // the current/next pattern is copied from redux's createStore code.
-  // TODO: refactor+expose that code to be reusable here?
-  let current = []
-  let next = []
-
-  return {
-    clear() {
-      next = CLEARED
-      current = CLEARED
-    },
-
-    notify() {
-      const listeners = current = next
-      for (let i = 0; i < listeners.length; i++) {
-        listeners[i]()
-      }
-    },
-
-    get() {
-      return next
-    },
-
-    subscribe(listener) {
-      let isSubscribed = true
-      if (next === current) next = current.slice()
-      next.push(listener)
-
-      return function unsubscribe() {
-        if (!isSubscribed || current === CLEARED) return
-        isSubscribed = false
-
-        if (next === current) next = current.slice()
-        next.splice(next.indexOf(listener), 1)
-      }
-    }
-  }
-}
-
-export default class Subscription {
-  constructor(store, parentSub, onStateChange) {
-    this.store = store
-    this.parentSub = parentSub
-    this.onStateChange = onStateChange
-    this.unsubscribe = null
-    this.listeners = nullListeners
-  }
-
-  addNestedSub(listener) {
-    this.trySubscribe()
-    return this.listeners.subscribe(listener)
-  }
-
-  notifyNestedSubs() {
-    this.listeners.notify()
-  }
-
-  isSubscribed() {
-    return Boolean(this.unsubscribe)
-  }
-
-  trySubscribe() {
-    if (!this.unsubscribe) {
-      this.unsubscribe = this.parentSub
-        ? this.parentSub.addNestedSub(this.onStateChange)
-        : this.store.subscribe(this.onStateChange)
- 
-      this.listeners = createListenerCollection()
-    }
-  }
-
-  tryUnsubscribe() {
-    if (this.unsubscribe) {
-      this.unsubscribe()
-      this.unsubscribe = null
-      this.listeners.clear()
-      this.listeners = nullListeners
-    }
-  }
-}


Test patch:
diff --git a/test/components/Provider.spec.js b/test/components/Provider.spec.js
index 9ada2ac..ddb0263 100644
--- a/test/components/Provider.spec.js
+++ b/test/components/Provider.spec.js
@@ -1,47 +1,39 @@
 /*eslint-disable react/prop-types*/
 
 import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import semver from 'semver'
+import ReactDOM from 'react-dom'
 import { createStore } from 'redux'
-import { Provider, createProvider, connect } from '../../src/index.js'
+import { Provider, connect } from '../../src/index.js'
+import { ReactReduxContext } from '../../src/components/Context'
 import * as rtl from 'react-testing-library'
 import 'jest-dom/extend-expect'
 
-const createExampleTextReducer = () => (state = "example text") => state;
+const createExampleTextReducer = () => (state = 'example text') => state
 
 describe('React', () => {
   describe('Provider', () => {
     afterEach(() => rtl.cleanup())
+
     const createChild = (storeKey = 'store') => {
       class Child extends Component {
         render() {
-          const store = this.context[storeKey];
-          
-          let text = '';
-          
-          if(store) {
-            text = store.getState().toString()
-          }
-          
           return (
-            <div data-testid="store">
-              {storeKey} - {text}
-            </div>
+            <ReactReduxContext.Consumer>
+              {({ storeState }) => {
+                return (
+                  <div data-testid="store">{`${storeKey} - ${storeState}`}</div>
+                )
+              }}
+            </ReactReduxContext.Consumer>
           )
         }
       }
 
-
-        Child.contextTypes = {
-          [storeKey]: PropTypes.object.isRequired
-        }
-
-        return Child
+      return Child
     }
-    const Child = createChild();
+    const Child = createChild()
 
-    it('should enforce a single child', () => {
+    it('should not enforce a single child', () => {
       const store = createStore(() => ({}))
 
       // Ignore propTypes warnings
@@ -50,47 +42,31 @@ describe('React', () => {
 
       const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
 
-      try {
-        expect(() => rtl.render(
+      expect(() =>
+        rtl.render(
           <Provider store={store}>
             <div />
           </Provider>
-        )).not.toThrow()
+        )
+      ).not.toThrow()
 
-        if (semver.lt(React.version, '15.0.0')) {
-          expect(() => rtl.render(
-            <Provider store={store}>
-            </Provider>
-          )).toThrow(/children with exactly one child/)
-        } else {
-          expect(() => rtl.render(
-            <Provider store={store}>
-            </Provider>
-          )).toThrow(/a single React element child/)
-        }
+      expect(() => rtl.render(<Provider store={store} />)).not.toThrow(
+        /children with exactly one child/
+      )
 
-        if (semver.lt(React.version, '15.0.0')) {
-          expect(() => rtl.render(
-            <Provider store={store}>
-              <div />
-              <div />
-            </Provider>
-          )).toThrow(/children with exactly one child/)
-        } else {
-          expect(() => rtl.render(
-            <Provider store={store}>
-              <div />
-              <div />
-            </Provider>
-          )).toThrow(/a single React element child/)
-        }
-      } finally {
-        Provider.propTypes = propTypes
-        spy.mockRestore()
-      }
+      expect(() =>
+        rtl.render(
+          <Provider store={store}>
+            <div />
+            <div />
+          </Provider>
+        )
+      ).not.toThrow(/a single React element child/)
+      spy.mockRestore()
+      Provider.propTypes = propTypes
     })
 
-    it('should add the store to the child context', () => {
+    it('should add the store state to context', () => {
       const store = createStore(createExampleTextReducer())
 
       const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
@@ -101,31 +77,16 @@ describe('React', () => {
       )
       expect(spy).toHaveBeenCalledTimes(0)
       spy.mockRestore()
-      
-      expect(tester.getByTestId('store')).toHaveTextContent('store - example text')
-    })
-
-    it('should add the store to the child context using a custom store key', () => {
-        const store = createStore(createExampleTextReducer())
-        const CustomProvider = createProvider('customStoreKey');
-        const CustomChild = createChild('customStoreKey');
 
-        const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
-      const tester = rtl.render(
-          <CustomProvider store={store}>
-            <CustomChild />
-          </CustomProvider>
-        )
-        expect(spy).toHaveBeenCalledTimes(0)
-        spy.mockRestore()
-
-      expect(tester.getByTestId('store')).toHaveTextContent('customStoreKey - example text')
+      expect(tester.getByTestId('store')).toHaveTextContent(
+        'store - example text'
+      )
     })
 
-    it('should warn once when receiving a new store in props', () => {
+    it('accepts new store in props', () => {
       const store1 = createStore((state = 10) => state + 1)
       const store2 = createStore((state = 10) => state * 2)
-      const store3 = createStore((state = 10) => state * state)
+      const store3 = createStore((state = 10) => state * state + 1)
 
       let externalSetState
       class ProviderContainer extends Component {
@@ -134,6 +95,7 @@ describe('React', () => {
           this.state = { store: store1 }
           externalSetState = this.setState.bind(this)
         }
+
         render() {
           return (
             <Provider store={this.state.store}>
@@ -145,132 +107,173 @@ describe('React', () => {
 
       const tester = rtl.render(<ProviderContainer />)
       expect(tester.getByTestId('store')).toHaveTextContent('store - 11')
+      store1.dispatch({ type: 'hi' })
+      expect(tester.getByTestId('store')).toHaveTextContent('store - 12')
 
-      let spy = jest.spyOn(console, 'error').mockImplementation(() => {})
       externalSetState({ store: store2 })
-      
-      expect(tester.getByTestId('store')).toHaveTextContent('store - 11')
-      expect(spy).toHaveBeenCalledTimes(1)
-      expect(spy.mock.calls[0][0]).toBe(
-        '<Provider> does not support changing `store` on the fly. ' +
-        'It is most likely that you see this error because you updated to ' +
-        'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' +
-        'automatically. See https://github.com/reduxjs/react-redux/releases/' +
-        'tag/v2.0.0 for the migration instructions.'
-      )
-      spy.mockRestore()
-      
-      spy = jest.spyOn(console, 'error').mockImplementation(() => {})
+      expect(tester.getByTestId('store')).toHaveTextContent('store - 20')
+      store1.dispatch({ type: 'hi' })
+      expect(tester.getByTestId('store')).toHaveTextContent('store - 20')
+      store2.dispatch({ type: 'hi' })
+      expect(tester.getByTestId('store')).toHaveTextContent('store - 40')
+
       externalSetState({ store: store3 })
-      
-      expect(tester.getByTestId('store')).toHaveTextContent('store - 11')
-      expect(spy).toHaveBeenCalledTimes(0)
-      spy.mockRestore()
+      expect(tester.getByTestId('store')).toHaveTextContent('store - 101')
+      store1.dispatch({ type: 'hi' })
+      expect(tester.getByTestId('store')).toHaveTextContent('store - 101')
+      store2.dispatch({ type: 'hi' })
+      expect(tester.getByTestId('store')).toHaveTextContent('store - 101')
+      store3.dispatch({ type: 'hi' })
+      expect(tester.getByTestId('store')).toHaveTextContent('store - 10202')
     })
 
     it('should handle subscriptions correctly when there is nested Providers', () => {
-      const reducer = (state = 0, action) => (action.type === 'INC' ? state + 1 : state)
+      const reducer = (state = 0, action) =>
+        action.type === 'INC' ? state + 1 : state
 
       const innerStore = createStore(reducer)
       const innerMapStateToProps = jest.fn(state => ({ count: state }))
       @connect(innerMapStateToProps)
       class Inner extends Component {
-        render() { return <div>{this.props.count}</div> }
+        render() {
+          return <div>{this.props.count}</div>
+        }
       }
 
       const outerStore = createStore(reducer)
       @connect(state => ({ count: state }))
       class Outer extends Component {
-        render() { return <Provider store={innerStore}><Inner /></Provider> }
+        render() {
+          return (
+            <Provider store={innerStore}>
+              <Inner />
+            </Provider>
+          )
+        }
       }
 
-      rtl.render(<Provider store={outerStore}><Outer /></Provider>)
+      rtl.render(
+        <Provider store={outerStore}>
+          <Outer />
+        </Provider>
+      )
       expect(innerMapStateToProps).toHaveBeenCalledTimes(1)
 
-      innerStore.dispatch({ type: 'INC'})
+      innerStore.dispatch({ type: 'INC' })
       expect(innerMapStateToProps).toHaveBeenCalledTimes(2)
     })
-  })
 
-  it('should pass state consistently to mapState', () => {
-    function stringBuilder(prev = '', action) {
-      return action.type === 'APPEND'
-        ? prev + action.body
-        : prev
-    }
+    it('should pass state consistently to mapState', () => {
+      function stringBuilder(prev = '', action) {
+        return action.type === 'APPEND' ? prev + action.body : prev
+      }
 
-    const store = createStore(stringBuilder)
+      const store = createStore(stringBuilder)
 
-    store.dispatch({ type: 'APPEND', body: 'a' })
-    let childMapStateInvokes = 0
+      store.dispatch({ type: 'APPEND', body: 'a' })
+      let childMapStateInvokes = 0
 
-    @connect(state => ({ state }), null, null, { withRef: true })
-    class Container extends Component {
-      emitChange() {
-        store.dispatch({ type: 'APPEND', body: 'b' })
-      }
+      @connect(state => ({ state }))
+      class Container extends Component {
+        emitChange() {
+          store.dispatch({ type: 'APPEND', body: 'b' })
+        }
 
-      render() {
-        return (
-          <div>
-            <button onClick={this.emitChange.bind(this)}>change</button>
-            <ChildContainer parentState={this.props.state} />
-          </div>
-        )
+        render() {
+          return (
+            <div>
+              <button onClick={this.emitChange.bind(this)}>change</button>
+              <ChildContainer parentState={this.props.state} />
+            </div>
+          )
+        }
       }
-    }
 
-    @connect((state, parentProps) => {
-      childMapStateInvokes++
-      // The state from parent props should always be consistent with the current state
-      expect(state).toEqual(parentProps.parentState)
-      return {}
-    })
-    class ChildContainer extends Component {
-      render() {
-        return <div />
+      const childCalls = []
+      @connect((state, parentProps) => {
+        childMapStateInvokes++
+        childCalls.push([state, parentProps.parentState])
+        // The state from parent props should always be consistent with the current state
+        return {}
+      })
+      class ChildContainer extends Component {
+        render() {
+          return <div />
+        }
       }
-    }
-
-    const tester = rtl.render(
-      <Provider store={store}>
-        <Container />
-      </Provider>
-    )
 
-    expect(childMapStateInvokes).toBe(1)
+      const tester = rtl.render(
+        <Provider store={store}>
+          <Container />
+        </Provider>
+      )
 
-    // The store state stays consistent when setState calls are batched
-    store.dispatch({ type: 'APPEND', body: 'c' })
-    expect(childMapStateInvokes).toBe(2)
+      expect(childMapStateInvokes).toBe(1)
+
+      // The store state stays consistent when setState calls are batched
+      store.dispatch({ type: 'APPEND', body: 'c' })
+      expect(childMapStateInvokes).toBe(2)
+      expect(childCalls).toEqual([['a', 'a'], ['ac', 'ac']])
+
+      // setState calls DOM handlers are batched
+      const button = tester.getByText('change')
+      rtl.fireEvent.click(button)
+      expect(childMapStateInvokes).toBe(3)
+
+      // Provider uses unstable_batchedUpdates() under the hood
+      store.dispatch({ type: 'APPEND', body: 'd' })
+      expect(childCalls).toEqual([
+        ['a', 'a'],
+        ['ac', 'ac'], // then store update is processed
+        ['acb', 'acb'], // then store update is processed
+        ['acbd', 'acbd'] // then store update is processed
+      ])
+      expect(childMapStateInvokes).toBe(4)
+    })
 
-    // setState calls DOM handlers are batched
-    const button = tester.getByText('change')
-    rtl.fireEvent.click(button)
-    expect(childMapStateInvokes).toBe(3)
+    it('works in <StrictMode> without warnings (React 16.3+)', () => {
+      if (!React.StrictMode) {
+        return
+      }
+      const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
+      const store = createStore(() => ({}))
 
-    // Provider uses unstable_batchedUpdates() under the hood
-    store.dispatch({ type: 'APPEND', body: 'd' })
-    expect(childMapStateInvokes).toBe(4)
-  })
+      rtl.render(
+        <React.StrictMode>
+          <Provider store={store}>
+            <div />
+          </Provider>
+        </React.StrictMode>
+      )
 
+      expect(spy).not.toHaveBeenCalled()
+    })
 
-  it.skip('works in <StrictMode> without warnings (React 16.3+)', () => {
-    if (!React.StrictMode) {
-      return
-    }
-    const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
-    const store = createStore(() => ({}))
+    it('should unsubscribe before unmounting', () => {
+      const store = createStore(createExampleTextReducer())
+      const subscribe = store.subscribe
+
+      // Keep track of unsubscribe by wrapping subscribe()
+      const spy = jest.fn(() => ({}))
+      store.subscribe = listener => {
+        const unsubscribe = subscribe(listener)
+        return () => {
+          spy()
+          return unsubscribe()
+        }
+      }
 
-    rtl.render(
-      <React.StrictMode>
+      const div = document.createElement('div')
+      ReactDOM.render(
         <Provider store={store}>
           <div />
-        </Provider>
-      </React.StrictMode>
-    )
+        </Provider>,
+        div
+      )
 
-    expect(spy).not.toHaveBeenCalled()
+      expect(spy).toHaveBeenCalledTimes(0)
+      ReactDOM.unmountComponentAtNode(div)
+      expect(spy).toHaveBeenCalledTimes(1)
+    })
   })
-
 })
diff --git a/test/components/connect.spec.js b/test/components/connect.spec.js
index 4a8c815..61bb5d0 100644
--- a/test/components/connect.spec.js
+++ b/test/components/connect.spec.js
@@ -1,11 +1,11 @@
 /*eslint-disable react/prop-types*/
 
-import React, { Children, Component } from 'react'
+import React, { Component } from 'react'
 import createClass from 'create-react-class'
 import PropTypes from 'prop-types'
 import ReactDOM from 'react-dom'
 import { createStore } from 'redux'
-import { createProvider, connect } from '../../src/index.js'
+import { Provider as ProviderMock, connect } from '../../src/index.js'
 import * as rtl from 'react-testing-library'
 import 'jest-dom/extend-expect'
 
@@ -27,26 +27,15 @@ describe('React', () => {
         return (
           <ul>
             {Object.keys(this.props).map(prop => (
-              <li title="prop" data-testid={prop} key={prop}>{propMapper(this.props[prop])}</li>
+              <li title="prop" data-testid={prop} key={prop}>
+                {propMapper(this.props[prop])}
+              </li>
             ))}
           </ul>
         )
       }
     }
 
-    class ProviderMock extends Component {
-      getChildContext() {
-        return { store: this.props.store }
-      }
-
-      render() {
-        return Children.only(this.props.children)
-      }
-    }
-    ProviderMock.childContextTypes = {
-      store: PropTypes.object.isRequired
-    }
-
     class ContextBoundStore {
       constructor(reducer) {
         this.reducer = reducer
@@ -61,7 +50,7 @@ describe('React', () => {
 
       subscribe(listener) {
         this.listeners.push(listener)
-        return (() => this.listeners.filter(l => l !== listener))
+        return () => this.listeners.filter(l => l !== listener)
       }
 
       dispatch(action) {
@@ -72,26 +61,25 @@ describe('React', () => {
     }
 
     function stringBuilder(prev = '', action) {
-      return action.type === 'APPEND'
-        ? prev + action.body
-        : prev
+      return action.type === 'APPEND' ? prev + action.body : prev
     }
 
     function imitateHotReloading(TargetClass, SourceClass, container) {
       // Crude imitation of hot reloading that does the job
-      Object.getOwnPropertyNames(SourceClass.prototype).filter(key =>
-        typeof SourceClass.prototype[key] === 'function'
-      ).forEach(key => {
-        if (key !== 'render' && key !== 'constructor') {
-          TargetClass.prototype[key] = SourceClass.prototype[key]
-        }
-      })
+      Object.getOwnPropertyNames(SourceClass.prototype)
+        .filter(key => typeof SourceClass.prototype[key] === 'function')
+        .forEach(key => {
+          if (key !== 'render' && key !== 'constructor') {
+            TargetClass.prototype[key] = SourceClass.prototype[key]
+          }
+        })
 
       container.forceUpdate()
     }
 
     afterEach(() => rtl.cleanup())
-    it('should receive the store in the context', () => {
+
+    it('should receive the store state in the context', () => {
       const store = createStore(() => ({ hi: 'there' }))
 
       @connect(state => state)
@@ -101,9 +89,11 @@ describe('React', () => {
         }
       }
 
-      const tester = rtl.render(<ProviderMock store={store}>
+      const tester = rtl.render(
+        <ProviderMock store={store}>
           <Container pass="through" />
-      </ProviderMock>)
+        </ProviderMock>
+      )
 
       expect(tester.getByTestId('hi')).toHaveTextContent('there')
     })
@@ -112,16 +102,18 @@ describe('React', () => {
       if (React.memo) {
         const store = createStore(() => ({ hi: 'there' }))
 
-        const Container = React.memo((props) => <Passthrough {...props} />)
-        const WrappedContainer = connect(state => state)(Container);
+        const Container = React.memo(props => <Passthrough {...props} />)
+        const WrappedContainer = connect(state => state)(Container)
 
-        const tester = rtl.render(<ProviderMock store={store}>
+        const tester = rtl.render(
+          <ProviderMock store={store}>
             <WrappedContainer pass="through" />
-        </ProviderMock>)
+          </ProviderMock>
+        )
 
         expect(tester.getByTestId('hi')).toHaveTextContent('there')
       }
-    });
+    })
 
     it('should pass state and props to the given component', () => {
       const store = createStore(() => ({
@@ -130,7 +122,10 @@ describe('React', () => {
         hello: 'world'
       }))
 
-      @connect(({ foo, baz }) => ({ foo, baz }), {})
+      @connect(
+        ({ foo, baz }) => ({ foo, baz }),
+        {}
+      )
       class Container extends Component {
         render() {
           return <Passthrough {...this.props} />
@@ -151,10 +146,10 @@ describe('React', () => {
     it('should subscribe class components to the store changes', () => {
       const store = createStore(stringBuilder)
 
-      @connect(state => ({ string: state }) )
+      @connect(state => ({ string: state }))
       class Container extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -163,7 +158,6 @@ describe('React', () => {
           <Container />
         </ProviderMock>
       )
-
       expect(tester.getByTestId('string')).toHaveTextContent('')
       store.dispatch({ type: 'APPEND', body: 'a' })
       expect(tester.getByTestId('string')).toHaveTextContent('a')
@@ -174,13 +168,14 @@ describe('React', () => {
     it('should subscribe pure function components to the store changes', () => {
       const store = createStore(stringBuilder)
 
-      const Container = connect(
-        state => ({ string: state })
-      )(function Container(props) {
-        return <Passthrough {...props}/>
-      })
+      const Container = connect(state => ({ string: state }))(
+        function Container(props) {
+          return <Passthrough {...props} />
+        }
+      )
 
       const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
+
       const tester = rtl.render(
         <ProviderMock store={store}>
           <Container />
@@ -196,13 +191,13 @@ describe('React', () => {
       expect(tester.getByTestId('string')).toHaveTextContent('ab')
     })
 
-    it('should retain the store\'s context', () => {
+    it("should retain the store's context", () => {
       const store = new ContextBoundStore(stringBuilder)
 
-      let Container = connect(
-        state => ({ string: state })
-      )(function Container(props) {
-        return <Passthrough {...props}/>
+      let Container = connect(state => ({ string: state }))(function Container(
+        props
+      ) {
+        return <Passthrough {...props} />
       })
 
       const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
@@ -222,23 +217,21 @@ describe('React', () => {
     it('should handle dispatches before componentDidMount', () => {
       const store = createStore(stringBuilder)
 
-      @connect(state => ({ string: state }) )
+      @connect(state => ({ string: state }))
       class Container extends Component {
         componentDidMount() {
           store.dispatch({ type: 'APPEND', body: 'a' })
         }
 
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
-
       const tester = rtl.render(
         <ProviderMock store={store}>
           <Container />
         </ProviderMock>
       )
-
       expect(tester.getByTestId('string')).toHaveTextContent('a')
     })
 
@@ -250,9 +243,7 @@ describe('React', () => {
       @connect(state => state)
       class ConnectContainer extends Component {
         render() {
-          return (
-            <Passthrough {...this.props} pass={this.props.bar.baz} />
-          )
+          return <Passthrough {...this.props} pass={this.props.bar.baz} />
         }
       }
 
@@ -276,7 +267,7 @@ describe('React', () => {
           return (
             <ProviderMock store={store}>
               <ConnectContainer bar={this.state.bar} />
-             </ProviderMock>
+            </ProviderMock>
           )
         }
       }
@@ -293,9 +284,7 @@ describe('React', () => {
       @connect(state => state)
       class ConnectContainer extends Component {
         render() {
-          return (
-            <Passthrough {...this.props} pass={this.props.bar} />
-          )
+          return <Passthrough {...this.props} pass={this.props.bar} />
         }
       }
 
@@ -308,13 +297,12 @@ describe('React', () => {
         componentDidMount() {
           this.bar = 'foo'
           this.forceUpdate()
-          this.c.forceUpdate()
         }
 
         render() {
           return (
             <ProviderMock store={store}>
-              <ConnectContainer bar={this.bar} ref={c => this.c = c} />
+              <ConnectContainer bar={this.bar} />
             </ProviderMock>
           )
         }
@@ -330,26 +318,25 @@ describe('React', () => {
       let props = { x: true }
       let container
 
-      @connect(() => ({}), () => ({}))
+      @connect(
+        () => ({}),
+        () => ({})
+      )
       class ConnectContainer extends Component {
         render() {
-          return (
-            <Passthrough {...this.props} />
-          )
+          return <Passthrough {...this.props} />
         }
       }
 
       class HolderContainer extends Component {
         render() {
-          return (
-            <ConnectContainer {...props} />
-          )
+          return <ConnectContainer {...props} />
         }
       }
 
       const tester = rtl.render(
         <ProviderMock store={store}>
-          <HolderContainer ref={instance => container = instance} />
+          <HolderContainer ref={instance => (container = instance)} />
         </ProviderMock>
       )
 
@@ -369,35 +356,35 @@ describe('React', () => {
       @connect(() => ({}))
       class ConnectContainer extends Component {
         render() {
-          return (
-            <Passthrough {...this.props} />
-          )
+          return <Passthrough {...this.props} />
         }
       }
 
       class HolderContainer extends Component {
         render() {
-          return (
-            <ConnectContainer {...props} />
-          )
+          return <ConnectContainer {...props} />
         }
       }
 
       const tester = rtl.render(
         <ProviderMock store={store}>
-          <HolderContainer ref={instance => container = instance} />
+          <HolderContainer ref={instance => (container = instance)} />
         </ProviderMock>
       )
 
       expect(tester.getAllByTitle('prop').length).toBe(2)
-      expect(tester.getByTestId('dispatch')).toHaveTextContent('[function dispatch]')
+      expect(tester.getByTestId('dispatch')).toHaveTextContent(
+        '[function dispatch]'
+      )
       expect(tester.getByTestId('x')).toHaveTextContent('true')
 
       props = {}
       container.forceUpdate()
 
       expect(tester.getAllByTitle('prop').length).toBe(1)
-      expect(tester.getByTestId('dispatch')).toHaveTextContent('[function dispatch]')
+      expect(tester.getByTestId('dispatch')).toHaveTextContent(
+        '[function dispatch]'
+      )
     })
 
     it('should ignore deep mutations in props', () => {
@@ -408,9 +395,7 @@ describe('React', () => {
       @connect(state => state)
       class ConnectContainer extends Component {
         render() {
-          return (
-            <Passthrough {...this.props} pass={this.props.bar.baz} />
-          )
+          return <Passthrough {...this.props} pass={this.props.bar.baz} />
         }
       }
 
@@ -442,7 +427,7 @@ describe('React', () => {
         }
       }
 
-      const tester = rtl.render(<Container/>)
+      const tester = rtl.render(<Container />)
       expect(tester.getByTestId('foo')).toHaveTextContent('bar')
       expect(tester.getByTestId('pass')).toHaveTextContent('')
     })
@@ -462,23 +447,23 @@ describe('React', () => {
       @connect(
         state => ({ stateThing: state }),
         dispatch => ({
-          doSomething: (whatever) => dispatch(doSomething(whatever))
+          doSomething: whatever => dispatch(doSomething(whatever))
         }),
         (stateProps, actionProps, parentProps) => ({
           ...stateProps,
           ...actionProps,
           mergedDoSomething: (() => {
             merged = function mergedDoSomething(thing) {
-            const seed = stateProps.stateThing === '' ? 'HELLO ' : ''
-            actionProps.doSomething(seed + thing + parentProps.extra)
-          }
+              const seed = stateProps.stateThing === '' ? 'HELLO ' : ''
+              actionProps.doSomething(seed + thing + parentProps.extra)
+            }
             return merged
           })()
         })
       )
       class Container extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -498,7 +483,7 @@ describe('React', () => {
         }
       }
 
-      const tester = rtl.render(<OuterContainer/>)
+      const tester = rtl.render(<OuterContainer />)
 
       expect(tester.getByTestId('stateThing')).toHaveTextContent('')
       merged('a')
@@ -515,7 +500,7 @@ describe('React', () => {
         foo: 'bar'
       }))
 
-      const exampleActionCreator = () => {};
+      const exampleActionCreator = () => {}
 
       @connect(
         state => state,
@@ -533,7 +518,9 @@ describe('React', () => {
         </ProviderMock>
       )
 
-      expect(tester.getByTestId('exampleActionCreator')).toHaveTextContent('[function exampleActionCreator]')
+      expect(tester.getByTestId('exampleActionCreator')).toHaveTextContent(
+        '[function exampleActionCreator]'
+      )
       expect(tester.getByTestId('foo')).toHaveTextContent('bar')
     })
 
@@ -543,14 +530,14 @@ describe('React', () => {
       let invocationCount = 0
 
       /*eslint-disable no-unused-vars */
-      @connect((arg1) => {
+      @connect(arg1 => {
         invocationCount++
         return {}
       })
       /*eslint-enable no-unused-vars */
       class WithoutProps extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -576,7 +563,7 @@ describe('React', () => {
       let outerComponent
       rtl.render(
         <ProviderMock store={store}>
-          <OuterComponent ref={c => outerComponent = c} />
+          <OuterComponent ref={c => (outerComponent = c)} />
         </ProviderMock>
       )
       outerComponent.setFoo('BAR')
@@ -594,10 +581,9 @@ describe('React', () => {
         invocationCount++
         return {}
       })
-
       class WithoutProps extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -623,7 +609,7 @@ describe('React', () => {
       let outerComponent
       rtl.render(
         <ProviderMock store={store}>
-          <OuterComponent ref={c => outerComponent = c} />
+          <OuterComponent ref={c => (outerComponent = c)} />
         </ProviderMock>
       )
       outerComponent.setFoo('BAR')
@@ -645,7 +631,7 @@ describe('React', () => {
       })
       class WithProps extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -671,7 +657,7 @@ describe('React', () => {
       let outerComponent
       rtl.render(
         <ProviderMock store={store}>
-          <OuterComponent ref={c => outerComponent = c} />
+          <OuterComponent ref={c => (outerComponent = c)} />
         </ProviderMock>
       )
 
@@ -690,14 +676,17 @@ describe('React', () => {
       let invocationCount = 0
 
       /*eslint-disable no-unused-vars */
-      @connect(null, (arg1) => {
-        invocationCount++
-        return {}
-      })
+      @connect(
+        null,
+        arg1 => {
+          invocationCount++
+          return {}
+        }
+      )
       /*eslint-enable no-unused-vars */
       class WithoutProps extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -723,7 +712,7 @@ describe('React', () => {
       let outerComponent
       rtl.render(
         <ProviderMock store={store}>
-          <OuterComponent ref={c => outerComponent = c} />
+          <OuterComponent ref={c => (outerComponent = c)} />
         </ProviderMock>
       )
 
@@ -738,14 +727,16 @@ describe('React', () => {
 
       let invocationCount = 0
 
-      @connect(null, () => {
-        invocationCount++
-        return {}
-      })
-
+      @connect(
+        null,
+        () => {
+          invocationCount++
+          return {}
+        }
+      )
       class WithoutProps extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -771,7 +762,7 @@ describe('React', () => {
       let outerComponent
       rtl.render(
         <ProviderMock store={store}>
-          <OuterComponent ref={c => outerComponent = c} />
+          <OuterComponent ref={c => (outerComponent = c)} />
         </ProviderMock>
       )
 
@@ -787,14 +778,17 @@ describe('React', () => {
       let propsPassedIn
       let invocationCount = 0
 
-      @connect(null, (dispatch, props) => {
-        invocationCount++
-        propsPassedIn = props
-        return {}
-      })
+      @connect(
+        null,
+        (dispatch, props) => {
+          invocationCount++
+          propsPassedIn = props
+          return {}
+        }
+      )
       class WithProps extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -820,7 +814,7 @@ describe('React', () => {
       let outerComponent
       rtl.render(
         <ProviderMock store={store}>
-          <OuterComponent ref={c => outerComponent = c} />
+          <OuterComponent ref={c => (outerComponent = c)} />
         </ProviderMock>
       )
 
@@ -851,7 +845,9 @@ describe('React', () => {
             <Container pass="through" />
           </ProviderMock>
         )
-        expect(tester.getByTestId('dispatch')).toHaveTextContent('[function dispatch]')
+        expect(tester.getByTestId('dispatch')).toHaveTextContent(
+          '[function dispatch]'
+        )
         expect(tester.queryByTestId('foo')).toBe(null)
         expect(tester.getByTestId('pass')).toHaveTextContent('through')
       }
@@ -861,43 +857,6 @@ describe('React', () => {
       runCheck(false, false, false)
     })
 
-    it('should unsubscribe before unmounting', () => {
-      const store = createStore(stringBuilder)
-      const subscribe = store.subscribe
-
-      // Keep track of unsubscribe by wrapping subscribe()
-      const spy = jest.fn(() => ({}))
-      store.subscribe = (listener) => {
-        const unsubscribe = subscribe(listener)
-        return () => {
-          spy()
-          return unsubscribe()
-        }
-      }
-
-      @connect(
-        state => ({ string: state }),
-        dispatch => ({ dispatch })
-      )
-      class Container extends Component {
-        render() {
-          return <Passthrough {...this.props} />
-        }
-      }
-
-      const div = document.createElement('div')
-      ReactDOM.render(
-        <ProviderMock store={store}>
-          <Container />
-        </ProviderMock>,
-        div
-      )
-
-      expect(spy).toHaveBeenCalledTimes(0)
-      ReactDOM.unmountComponentAtNode(div)
-      expect(spy).toHaveBeenCalledTimes(1)
-    })
-
     it('should not attempt to set state after unmounting', () => {
       const store = createStore(stringBuilder)
       let mapStateToPropsCalls = 0
@@ -913,9 +872,7 @@ describe('React', () => {
       }
 
       const div = document.createElement('div')
-      store.subscribe(() =>
-        ReactDOM.unmountComponentAtNode(div)
-      )
+      store.subscribe(() => ReactDOM.unmountComponentAtNode(div))
       ReactDOM.render(
         <ProviderMock store={store}>
           <Container />
@@ -934,7 +891,7 @@ describe('React', () => {
     it('should not attempt to notify unmounted child of state change', () => {
       const store = createStore(stringBuilder)
 
-      @connect((state) => ({ hide: state === 'AB' }))
+      @connect(state => ({ hide: state === 'AB' }))
       class App extends Component {
         render() {
           return this.props.hide ? null : <Container />
@@ -944,21 +901,19 @@ describe('React', () => {
       @connect(() => ({}))
       class Container extends Component {
         render() {
-          return (
-            <Child />
-          )
+          return <Child />
         }
       }
 
-      @connect((state) => ({ state }))
+      @connect(state => ({ state }))
       class Child extends Component {
         componentDidMount() {
           if (this.props.state === 'A') {
-            store.dispatch({ type: 'APPEND', body: 'B' });
+            store.dispatch({ type: 'APPEND', body: 'B' })
           }
         }
         render() {
-          return null;
+          return null
         }
       }
 
@@ -991,8 +946,24 @@ describe('React', () => {
         /* eslint-disable react/jsx-no-bind */
         return (
           <div>
-            <a href="#" onClick={onClick('a')} ref={c => { linkA = c }}>A</a>
-            <a href="#" onClick={onClick('b')} ref={c => { linkB = c }}>B</a>
+            <a
+              href="#"
+              onClick={onClick('a')}
+              ref={c => {
+                linkA = c
+              }}
+            >
+              A
+            </a>
+            <a
+              href="#"
+              onClick={onClick('b')}
+              ref={c => {
+                linkB = c
+              }}
+            >
+              B
+            </a>
             {children}
           </div>
         )
@@ -1000,13 +971,10 @@ describe('React', () => {
       }
       App = connect(() => ({}))(App)
 
-
-      let A = () => (<h1>A</h1>)
+      let A = () => <h1>A</h1>
       A = connect(() => ({ calls: ++mapStateToPropsCalls }))(A)
 
-
-      const B = () => (<h1>B</h1>)
-
+      const B = () => <h1>B</h1>
 
       class RouterMock extends React.Component {
         constructor(...args) {
@@ -1022,26 +990,30 @@ describe('React', () => {
 
         getChildComponent(location) {
           switch (location) {
-            case 'a': return <A />
-            case 'b': return <B />
-            default: throw new Error('Unknown location: ' + location)
+            case 'a':
+              return <A />
+            case 'b':
+              return <B />
+            default:
+              throw new Error('Unknown location: ' + location)
           }
         }
 
         render() {
-          return (<App setLocation={this.setLocation}>
-            {this.getChildComponent(this.state.location.pathname)}
-          </App>)
+          return (
+            <App setLocation={this.setLocation}>
+              {this.getChildComponent(this.state.location.pathname)}
+            </App>
+          )
         }
       }
 
-
       const div = document.createElement('div')
       document.body.appendChild(div)
       ReactDOM.render(
-        (<ProviderMock store={store}>
+        <ProviderMock store={store}>
           <RouterMock />
-        </ProviderMock>),
+        </ProviderMock>,
         div
       )
 
@@ -1052,7 +1024,7 @@ describe('React', () => {
       linkB.click()
 
       document.body.removeChild(div)
-      expect(mapStateToPropsCalls).toBe(3)
+      expect(mapStateToPropsCalls).toBe(2)
       expect(spy).toHaveBeenCalledTimes(0)
       spy.mockRestore()
     })
@@ -1063,7 +1035,7 @@ describe('React', () => {
 
       /*eslint-disable no-unused-vars */
       @connect(
-        (state) => ({ calls: mapStateToPropsCalls++ }),
+        state => ({ calls: mapStateToPropsCalls++ }),
         dispatch => ({ dispatch })
       )
       /*eslint-enable no-unused-vars */
@@ -1097,7 +1069,7 @@ describe('React', () => {
       const spy = jest.fn(() => ({}))
       function render({ string }) {
         spy()
-        return <Passthrough string={string}/>
+        return <Passthrough string={string} />
       }
 
       @connect(
@@ -1115,7 +1087,6 @@ describe('React', () => {
           <Container />
         </ProviderMock>
       )
-
       expect(spy).toHaveBeenCalledTimes(1)
       expect(tester.getByTestId('string')).toHaveTextContent('')
       store.dispatch({ type: 'APPEND', body: 'a' })
@@ -1206,26 +1177,32 @@ describe('React', () => {
       tree.setState({ pass: obj2 })
       expect(spy).toHaveBeenCalledTimes(5)
       expect(tester.getByTestId('string')).toHaveTextContent('a')
-      expect(tester.getByTestId('pass')).toHaveTextContent('{"prop":"val","val":"otherval"}')
+      expect(tester.getByTestId('pass')).toHaveTextContent(
+        '{"prop":"val","val":"otherval"}'
+      )
 
       obj2.val = 'mutation'
       tree.setState({ pass: obj2 })
       expect(spy).toHaveBeenCalledTimes(5)
       expect(tester.getByTestId('string')).toHaveTextContent('a')
-      expect(tester.getByTestId('pass')).toHaveTextContent('{"prop":"val","val":"otherval"}')
+      expect(tester.getByTestId('pass')).toHaveTextContent(
+        '{"prop":"val","val":"otherval"}'
+      )
     })
 
     it('should throw an error if a component is not passed to the function returned by connect', () => {
-      expect(connect()).toThrow(
-        /You must pass a component to the function/
-      )
+      expect(connect()).toThrow(/You must pass a component to the function/)
     })
 
     it('should throw an error if mapState, mapDispatch, or mergeProps returns anything but a plain object', () => {
       const store = createStore(() => ({}))
 
       function makeContainer(mapState, mapDispatch, mergeProps) {
-        @connect(mapState, mapDispatch, mergeProps)
+        @connect(
+          mapState,
+          mapDispatch,
+          mergeProps
+        )
         class Container extends Component {
           render() {
             return <Passthrough />
@@ -1234,7 +1211,7 @@ describe('React', () => {
         return React.createElement(Container)
       }
 
-      function AwesomeMap() { }
+      function AwesomeMap() {}
 
       let spy = jest.spyOn(console, 'error').mockImplementation(() => {})
       rtl.render(
@@ -1352,67 +1329,54 @@ describe('React', () => {
       )
       spy.mockRestore()
     })
-
-    it('should recalculate the state and rebind the actions on hot update', () => {
+    it.skip('should recalculate the state and rebind the actions on hot update', () => {
       const store = createStore(() => {})
-
       @connect(
         null,
         () => ({ scooby: 'doo' })
       )
       class ContainerBefore extends Component {
         render() {
-          return (
-            <Passthrough {...this.props} />
-          )
+          return <Passthrough {...this.props} />
         }
       }
-
       @connect(
         () => ({ foo: 'baz' }),
         () => ({ scooby: 'foo' })
       )
       class ContainerAfter extends Component {
         render() {
-          return (
-            <Passthrough {...this.props} />
-          )
+          return <Passthrough {...this.props} />
         }
       }
-
       @connect(
         () => ({ foo: 'bar' }),
         () => ({ scooby: 'boo' })
       )
       class ContainerNext extends Component {
         render() {
-          return (
-            <Passthrough {...this.props} />
-          )
+          return <Passthrough {...this.props} />
         }
       }
-
       let container
       const tester = rtl.render(
         <ProviderMock store={store}>
-          <ContainerBefore ref={instance => container = instance} />
+          <ContainerBefore ref={instance => (container = instance)} />
         </ProviderMock>
       )
       expect(tester.queryByTestId('foo')).toBe(null)
       expect(tester.getByTestId('scooby')).toHaveTextContent('doo')
-
       imitateHotReloading(ContainerBefore, ContainerAfter, container)
       expect(tester.getByTestId('foo')).toHaveTextContent('baz')
       expect(tester.getByTestId('scooby')).toHaveTextContent('foo')
-
       imitateHotReloading(ContainerBefore, ContainerNext, container)
       expect(tester.getByTestId('foo')).toHaveTextContent('bar')
       expect(tester.getByTestId('scooby')).toHaveTextContent('boo')
     })
 
-    it('should persist listeners through hot update', () => {
-      const ACTION_TYPE = "ACTION"
-      const store = createStore((state = {actions: 0}, action) => {
+    it.skip('should persist listeners through hot update', () => {
+      const ACTION_TYPE = 'ACTION'
+      const store = createStore((state = { actions: 0 }, action) => {
         switch (action.type) {
           case ACTION_TYPE: {
             return {
@@ -1424,80 +1388,76 @@ describe('React', () => {
         }
       })
 
-      @connect(
-        (state) => ({ actions: state.actions })
-      )
+      @connect(state => ({ actions: state.actions }))
       class Child extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
-      @connect(
-        () => ({ scooby: 'doo' })
-      )
+      @connect(() => ({ scooby: 'doo' }))
       class ParentBefore extends Component {
         render() {
-          return (
-            <Child />
-          )
+          return <Child />
         }
       }
 
-      @connect(
-        () => ({ scooby: 'boo' })
-      )
+      @connect(() => ({ scooby: 'boo' }))
       class ParentAfter extends Component {
         render() {
-          return (
-            <Child />
-          )
+          return <Child />
         }
       }
 
       let container
       const tester = rtl.render(
         <ProviderMock store={store}>
-          <ParentBefore ref={instance => container = instance}/>
+          <ParentBefore ref={instance => (container = instance)} />
         </ProviderMock>
       )
 
       imitateHotReloading(ParentBefore, ParentAfter, container)
 
-      store.dispatch({type: ACTION_TYPE})
+      store.dispatch({ type: ACTION_TYPE })
 
       expect(tester.getByTestId('actions')).toHaveTextContent('1')
     })
 
     it('should set the displayName correctly', () => {
-      expect(connect(state => state)(
-        class Foo extends Component {
-          render() {
-            return <div />
+      expect(
+        connect(state => state)(
+          class Foo extends Component {
+            render() {
+              return <div />
+            }
           }
-        }
-      ).displayName).toBe('Connect(Foo)')
+        ).displayName
+      ).toBe('Connect(Foo)')
 
-      expect(connect(state => state)(
-        createClass({
-          displayName: 'Bar',
-          render() {
-            return <div />
-          }
-        })
-      ).displayName).toBe('Connect(Bar)')
+      expect(
+        connect(state => state)(
+          createClass({
+            displayName: 'Bar',
+            render() {
+              return <div />
+            }
+          })
+        ).displayName
+      ).toBe('Connect(Bar)')
 
-      expect(connect(state => state)(
-        // eslint: In this case, we don't want to specify a displayName because we're testing what
-        // happens when one isn't defined.
-        /* eslint-disable react/display-name */
-        createClass({
-          render() {
-            return <div />
-          }
-        })
-        /* eslint-enable react/display-name */
-      ).displayName).toBe('Connect(Component)')
+      expect(
+        connect(state => state)(
+          // eslint: In this case, we don't want to specify a displayName because we're testing what
+          // happens when one isn't defined.
+          /* eslint-disable react/display-name */
+          createClass({
+            render() {
+              return <div />
+            }
+          })
+          /* eslint-enable react/display-name */
+        ).displayName
+      ).toBe('Connect(Component)')
     })
 
     it('should expose the wrapped component as WrappedComponent', () => {
@@ -1531,35 +1491,82 @@ describe('React', () => {
       expect(decorated.foo).toBe('bar')
     })
 
-    it('should use the store from the props instead of from the context if present', () => {
+    it('should use a custom context provider and consumer if given as an option to connect', () => {
       class Container extends Component {
         render() {
           return <Passthrough />
         }
       }
 
+      const context = React.createContext(null)
+
       let actualState
 
       const expectedState = { foos: {} }
+      const ignoredState = { bars: {} }
+
+      const decorator = connect(
+        state => {
+          actualState = state
+          return {}
+        },
+        undefined,
+        undefined,
+        { context }
+      )
+      const Decorated = decorator(Container)
+
+      const store1 = createStore(() => expectedState)
+      const store2 = createStore(() => ignoredState)
+
+      rtl.render(
+        <ProviderMock context={context} store={store1}>
+          <ProviderMock store={store2}>
+            <Decorated />
+          </ProviderMock>
+        </ProviderMock>
+      )
+
+      expect(actualState).toEqual(expectedState)
+    })
+
+    it('should use a custom context provider and consumer if passed as a prop to the component', () => {
+      class Container extends Component {
+        render() {
+          return <Passthrough />
+        }
+      }
+
+      const context = React.createContext(null)
+
+      let actualState
+
+      const expectedState = { foos: {} }
+      const ignoredState = { bars: {} }
+
       const decorator = connect(state => {
         actualState = state
         return {}
       })
       const Decorated = decorator(Container)
-      const mockStore = {
-        dispatch: () => {},
-        subscribe: () => {},
-        getState: () => expectedState
-      }
 
-      rtl.render(<Decorated store={mockStore} />)
+      const store1 = createStore(() => expectedState)
+      const store2 = createStore(() => ignoredState)
+
+      rtl.render(
+        <ProviderMock context={context} store={store1}>
+          <ProviderMock store={store2}>
+            <Decorated context={context} />
+          </ProviderMock>
+        </ProviderMock>
+      )
 
       expect(actualState).toEqual(expectedState)
     })
 
-    it('should throw an error if the store is not in the props or context', () => {
+    it.skip('should throw an error if the store is not in the props or context', () => {
       const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
-      
+
       class Container extends Component {
         render() {
           return <Passthrough />
@@ -1569,16 +1576,12 @@ describe('React', () => {
       const decorator = connect(() => {})
       const Decorated = decorator(Container)
 
-      expect(() =>
-        rtl.render(<Decorated />)
-      ).toThrow(
-        /Could not find "store"/
-      )
+      expect(() => rtl.render(<Decorated />)).toThrow(/Could not find "store"/)
 
       spy.mockRestore()
     })
 
-    it('should throw when trying to access the wrapped instance if withRef is not specified', () => {
+    it.skip('should throw when trying to access the wrapped instance if withRef is not specified', () => {
       const store = createStore(() => ({}))
 
       class Container extends Component {
@@ -1592,29 +1595,70 @@ describe('React', () => {
 
       class Wrapper extends Component {
         render() {
-          return (
-            <Decorated ref={comp => comp && comp.getWrappedInstance()}/>
-          )
+          return <Decorated ref={comp => comp && comp.getWrappedInstance()} />
         }
       }
 
       // TODO Remove this when React is fixed, per https://github.com/facebook/react/issues/11098
       const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
+      expect(() =>
+        rtl.render(
+          <ProviderMock store={store}>
+            <Wrapper />
+          </ProviderMock>
+        )
+      ).toThrow(
+        `To access the wrapped instance, you need to specify { withRef: true } in the options argument of the connect() call`
+      )
+      spy.mockRestore()
+    })
 
-      expect(() => rtl.render(
+    it('should return the instance of the wrapped component for use in calling child methods', async done => {
+      const store = createStore(() => ({}))
+
+      const someData = {
+        some: 'data'
+      }
+
+      class Container extends Component {
+        someInstanceMethod() {
+          return someData
+        }
+
+        render() {
+          return <Passthrough loaded="yes" />
+        }
+      }
+
+      const decorator = connect(
+        state => state,
+        null,
+        null,
+        { forwardRef: true }
+      )
+      const Decorated = decorator(Container)
+
+      const ref = React.createRef()
+
+      class Wrapper extends Component {
+        render() {
+          return <Decorated ref={ref} />
+        }
+      }
+
+      const tester = rtl.render(
         <ProviderMock store={store}>
           <Wrapper />
         </ProviderMock>
-      )).toThrow(
-        `To access the wrapped instance, you need to specify { withRef: true } in the options argument of the connect() call`
       )
 
-      spy.mockRestore()
-
+      await rtl.waitForElement(() => tester.getByTestId('loaded'))
 
+      expect(ref.current.someInstanceMethod()).toBe(someData)
+      done()
     })
 
-    it('should return the instance of the wrapped component for use in calling child methods', async (done) => {
+    it('should return the instance of the wrapped component for use in calling child methods, impure component', async done => {
       const store = createStore(() => ({}))
 
       const someData = {
@@ -1631,30 +1675,31 @@ describe('React', () => {
         }
       }
 
-      const decorator = connect(state => state, null, null, { withRef: true })
+      const decorator = connect(
+        state => state,
+        undefined,
+        undefined,
+        { forwardRef: true, pure: false }
+      )
       const Decorated = decorator(Container)
 
-      let ref
+      const ref = React.createRef()
+
       class Wrapper extends Component {
         render() {
-          return (
-            <Decorated ref={comp => {
-              if (!comp) return
-              ref = comp.getWrappedInstance()
-            }}/>
-          )
+          return <Decorated ref={ref} />
         }
       }
+
       const tester = rtl.render(
         <ProviderMock store={store}>
           <Wrapper />
         </ProviderMock>
       )
 
-
       await rtl.waitForElement(() => tester.getByTestId('loaded'))
 
-      expect(ref.someInstanceMethod()).toBe(someData)
+      expect(ref.current.someInstanceMethod()).toBe(someData)
       done()
     })
 
@@ -1671,7 +1716,12 @@ describe('React', () => {
         statefulValue: PropTypes.number
       }
 
-      const decorator = connect(state => state, null, null, { pure: false })
+      const decorator = connect(
+        state => state,
+        null,
+        null,
+        { pure: false }
+      )
       const Decorated = decorator(ImpureComponent)
 
       let externalSetState
@@ -1750,24 +1800,22 @@ describe('React', () => {
         }
       }
 
-
       const tester = rtl.render(
         <ProviderMock store={store}>
           <StatefulWrapper />
         </ProviderMock>
       )
 
-
-      expect(mapStateSpy).toHaveBeenCalledTimes(2)
-      expect(mapDispatchSpy).toHaveBeenCalledTimes(2)
+      expect(mapStateSpy).toHaveBeenCalledTimes(1)
+      expect(mapDispatchSpy).toHaveBeenCalledTimes(1)
       expect(tester.getByTestId('statefulValue')).toHaveTextContent('foo')
 
       // Impure update
       storeGetter.storeKey = 'bar'
       externalSetState({ storeGetter })
 
-      expect(mapStateSpy).toHaveBeenCalledTimes(3)
-      expect(mapDispatchSpy).toHaveBeenCalledTimes(3)
+      expect(mapStateSpy).toHaveBeenCalledTimes(2)
+      expect(mapDispatchSpy).toHaveBeenCalledTimes(2)
       expect(tester.getByTestId('statefulValue')).toHaveTextContent('bar')
     })
 
@@ -1777,9 +1825,8 @@ describe('React', () => {
       store.dispatch({ type: 'APPEND', body: 'a' })
       let childMapStateInvokes = 0
 
-      @connect(state => ({ state }), null, null, { withRef: true })
+      @connect(state => ({ state }))
       class Container extends Component {
-
         emitChange() {
           store.dispatch({ type: 'APPEND', body: 'b' })
         }
@@ -1794,15 +1841,17 @@ describe('React', () => {
         }
       }
 
+      const childCalls = []
       @connect((state, parentProps) => {
         childMapStateInvokes++
+        childCalls.push([state, parentProps.parentState])
         // The state from parent props should always be consistent with the current state
         expect(state).toEqual(parentProps.parentState)
         return {}
       })
       class ChildContainer extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -1813,12 +1862,14 @@ describe('React', () => {
       )
 
       expect(childMapStateInvokes).toBe(1)
+      expect(childCalls).toEqual([['a', 'a']])
 
       // The store state stays consistent when setState calls are batched
       ReactDOM.unstable_batchedUpdates(() => {
         store.dispatch({ type: 'APPEND', body: 'c' })
       })
       expect(childMapStateInvokes).toBe(2)
+      expect(childCalls).toEqual([['a', 'a'], ['ac', 'ac']])
 
       // setState calls DOM handlers are batched
       const button = tester.getByText('change')
@@ -1827,6 +1878,12 @@ describe('React', () => {
 
       store.dispatch({ type: 'APPEND', body: 'd' })
       expect(childMapStateInvokes).toBe(4)
+      expect(childCalls).toEqual([
+        ['a', 'a'],
+        ['ac', 'ac'],
+        ['acb', 'acb'],
+        ['acbd', 'acbd']
+      ])
     })
 
     it('should not render the wrapped component when mapState does not produce change', () => {
@@ -1887,24 +1944,17 @@ describe('React', () => {
       expect(renderCalls).toBe(1)
       expect(mapStateCalls).toBe(1)
 
-      const spy = jest.spyOn(Container.prototype, 'setState')
-
       store.dispatch({ type: 'APPEND', body: 'a' })
       expect(mapStateCalls).toBe(2)
       expect(renderCalls).toBe(1)
-      expect(spy).toHaveBeenCalledTimes(0)
 
       store.dispatch({ type: 'APPEND', body: 'a' })
       expect(mapStateCalls).toBe(3)
       expect(renderCalls).toBe(1)
-      expect(spy).toHaveBeenCalledTimes(0)
 
       store.dispatch({ type: 'APPEND', body: 'a' })
       expect(mapStateCalls).toBe(4)
       expect(renderCalls).toBe(2)
-      expect(spy).toHaveBeenCalledTimes(1)
-
-      spy.mockRestore()
     })
 
     it('should not swallow errors when bailing out early', () => {
@@ -1936,9 +1986,7 @@ describe('React', () => {
 
       expect(renderCalls).toBe(1)
       expect(mapStateCalls).toBe(1)
-      expect(
-        () => store.dispatch({ type: 'APPEND', body: 'a' })
-      ).toThrow()
+      expect(() => store.dispatch({ type: 'APPEND', body: 'a' })).toThrow()
 
       spy.mockRestore()
     })
@@ -1957,7 +2005,9 @@ describe('React', () => {
           }
           lastProp = props.name
           lastVal = state.value
-          return lastResult = { someObject: { prop: props.name, stateVal: state.value } }
+          return (lastResult = {
+            someObject: { prop: props.name, stateVal: state.value }
+          })
         }
       }
 
@@ -1991,12 +2041,12 @@ describe('React', () => {
       let initialState
       let initialOwnProps
       let secondaryOwnProps
-      const mapStateFactory = function (factoryInitialState) {
+      const mapStateFactory = function(factoryInitialState) {
         initialState = factoryInitialState
-        initialOwnProps = arguments[1];
+        initialOwnProps = arguments[1]
         return (state, props) => {
           secondaryOwnProps = props
-          return { }
+          return {}
         }
       }
 
@@ -2019,7 +2069,7 @@ describe('React', () => {
       expect(initialOwnProps).toBe(undefined)
       expect(initialState).not.toBe(undefined)
       expect(secondaryOwnProps).not.toBe(undefined)
-      expect(secondaryOwnProps.name).toBe("a")
+      expect(secondaryOwnProps.name).toBe('a')
     })
 
     it('should allow providing a factory function to mapDispatchToProps', () => {
@@ -2035,14 +2085,18 @@ describe('React', () => {
             return lastResult
           }
           lastProp = props.name
-          return lastResult = { someObject: { dispatchFn: dispatch } }
+          return (lastResult = { someObject: { dispatchFn: dispatch } })
         }
       }
       function mergeParentDispatch(stateProps, dispatchProps, parentProps) {
         return { ...stateProps, ...dispatchProps, name: parentProps.name }
       }
 
-      @connect(null, mapDispatchFactory, mergeParentDispatch)
+      @connect(
+        null,
+        mapDispatchFactory,
+        mergeParentDispatch
+      )
       class Passthrough extends Component {
         componentDidUpdate() {
           updatedCount++
@@ -2087,7 +2141,11 @@ describe('React', () => {
       let renderCalls = 0
       const store = createStore(stringBuilder)
 
-      @connect(() => ({ a: ++mapStateCalls }), null, () => ({ changed: false }))
+      @connect(
+        () => ({ a: ++mapStateCalls }),
+        null,
+        () => ({ changed: false })
+      )
       class Container extends Component {
         render() {
           renderCalls++
@@ -2114,7 +2172,12 @@ describe('React', () => {
       let store = createStore(() => ({}))
       let renderCount = 0
 
-      @connect(null, null, () => ({ a: 1 }), { pure: false })
+      @connect(
+        null,
+        null,
+        () => ({ a: 1 }),
+        { pure: false }
+      )
       class Container extends React.Component {
         render() {
           ++renderCount
@@ -2188,10 +2251,15 @@ describe('React', () => {
     })
 
     it('should allow custom displayName', () => {
-      @connect(null, null, null, { getDisplayName: name => `Custom(${name})` })
+      @connect(
+        null,
+        null,
+        null,
+        { getDisplayName: name => `Custom(${name})` }
+      )
       class MyComponent extends React.Component {
         render() {
-          return <div></div>
+          return <div />
         }
       }
 
@@ -2202,7 +2270,12 @@ describe('React', () => {
       const store = createStore(() => ({}))
       let renderCount = 0
 
-      @connect(() => ({}), null, null, { pure: false })
+      @connect(
+        () => ({}),
+        null,
+        null,
+        { pure: false }
+      )
       class ImpureComponent extends React.Component {
         render() {
           ++renderCount
@@ -2241,7 +2314,11 @@ describe('React', () => {
 
     it('should throw a helpful error for invalid mapStateToProps arguments', () => {
       @connect('invalid')
-      class InvalidMapState extends React.Component { render() { return <div></div> } }
+      class InvalidMapState extends React.Component {
+        render() {
+          return <div />
+        }
+      }
 
       const error = renderWithBadConnect(InvalidMapState)
       expect(error).toContain('string')
@@ -2250,8 +2327,15 @@ describe('React', () => {
     })
 
     it('should throw a helpful error for invalid mapDispatchToProps arguments', () => {
-      @connect(null, 'invalid')
-      class InvalidMapDispatch extends React.Component { render() { return <div></div> } }
+      @connect(
+        null,
+        'invalid'
+      )
+      class InvalidMapDispatch extends React.Component {
+        render() {
+          return <div />
+        }
+      }
 
       const error = renderWithBadConnect(InvalidMapDispatch)
       expect(error).toContain('string')
@@ -2260,8 +2344,16 @@ describe('React', () => {
     })
 
     it('should throw a helpful error for invalid mergeProps arguments', () => {
-      @connect(null, null, 'invalid')
-      class InvalidMerge extends React.Component { render() { return <div></div> } }
+      @connect(
+        null,
+        null,
+        'invalid'
+      )
+      class InvalidMerge extends React.Component {
+        render() {
+          return <div />
+        }
+      }
 
       const error = renderWithBadConnect(InvalidMerge)
       expect(error).toContain('string')
@@ -2272,22 +2364,40 @@ describe('React', () => {
     it('should notify nested components through a blocking component', () => {
       @connect(state => ({ count: state }))
       class Parent extends Component {
-        render() { return <BlockUpdates><Child /></BlockUpdates> }
+        render() {
+          return (
+            <BlockUpdates>
+              <Child />
+            </BlockUpdates>
+          )
+        }
       }
 
       class BlockUpdates extends Component {
-        shouldComponentUpdate() { return false; }
-        render() { return this.props.children; }
+        shouldComponentUpdate() {
+          return false
+        }
+        render() {
+          return this.props.children
+        }
       }
 
       const mapStateToProps = jest.fn(state => ({ count: state }))
       @connect(mapStateToProps)
       class Child extends Component {
-        render() { return <div>{this.props.count}</div> }
+        render() {
+          return <div>{this.props.count}</div>
+        }
       }
 
-      const store = createStore((state = 0, action) => (action.type === 'INC' ? state + 1 : state))
-      rtl.render(<ProviderMock store={store}><Parent /></ProviderMock>)
+      const store = createStore(
+        (state = 0, action) => (action.type === 'INC' ? state + 1 : state)
+      )
+      rtl.render(
+        <ProviderMock store={store}>
+          <Parent />
+        </ProviderMock>
+      )
 
       expect(mapStateToProps).toHaveBeenCalledTimes(1)
       store.dispatch({ type: 'INC' })
@@ -2295,53 +2405,104 @@ describe('React', () => {
     })
 
     it('should subscribe properly when a middle connected component does not subscribe', () => {
-
       @connect(state => ({ count: state }))
-      class A extends React.Component { render() { return <B {...this.props} /> }}
+      class A extends React.Component {
+        render() {
+          return <B {...this.props} />
+        }
+      }
 
       @connect() // no mapStateToProps. therefore it should be transparent for subscriptions
-      class B extends React.Component { render() { return <C {...this.props} /> }}
+      class B extends React.Component {
+        render() {
+          return <C {...this.props} />
+        }
+      }
 
       @connect((state, props) => {
         expect(props.count).toBe(state)
         return { count: state * 10 + props.count }
       })
-      class C extends React.Component { render() { return <div>{this.props.count}</div> }}
+      class C extends React.Component {
+        render() {
+          return <div>{this.props.count}</div>
+        }
+      }
 
-      const store = createStore((state = 0, action) => (action.type === 'INC' ? state += 1 : state))
-      rtl.render(<ProviderMock store={store}><A /></ProviderMock>)
+      const store = createStore(
+        (state = 0, action) => (action.type === 'INC' ? (state += 1) : state)
+      )
+      rtl.render(
+        <ProviderMock store={store}>
+          <A />
+        </ProviderMock>
+      )
 
       store.dispatch({ type: 'INC' })
     })
 
     it('should subscribe properly when a new store is provided via props', () => {
-      const store1 = createStore((state = 0, action) => (action.type === 'INC' ? state + 1 : state))
-      const store2 = createStore((state = 0, action) => (action.type === 'INC' ? state + 1 : state))
+      const store1 = createStore(
+        (state = 0, action) => (action.type === 'INC' ? state + 1 : state)
+      )
+      const store2 = createStore(
+        (state = 0, action) => (action.type === 'INC' ? state + 1 : state)
+      )
+      const customContext = React.createContext()
 
-      @connect(state => ({ count: state }))
+      @connect(
+        state => ({ count: state }),
+        undefined,
+        undefined,
+        { context: customContext }
+      )
       class A extends Component {
-        render() { return <B store={store2} /> }
+        render() {
+          return <B />
+        }
       }
 
       const mapStateToPropsB = jest.fn(state => ({ count: state }))
-      @connect(mapStateToPropsB)
+      @connect(
+        mapStateToPropsB,
+        undefined,
+        undefined,
+        { context: customContext }
+      )
       class B extends Component {
-        render() { return <C {...this.props} /> }
+        render() {
+          return <C {...this.props} />
+        }
       }
 
       const mapStateToPropsC = jest.fn(state => ({ count: state }))
-      @connect(mapStateToPropsC)
+      @connect(
+        mapStateToPropsC,
+        undefined,
+        undefined,
+        { context: customContext }
+      )
       class C extends Component {
-        render() { return <D /> }
+        render() {
+          return <D />
+        }
       }
 
       const mapStateToPropsD = jest.fn(state => ({ count: state }))
       @connect(mapStateToPropsD)
       class D extends Component {
-        render() { return <div>{this.props.count}</div> }
+        render() {
+          return <div>{this.props.count}</div>
+        }
       }
 
-      rtl.render(<ProviderMock store={store1}><A /></ProviderMock>)
+      rtl.render(
+        <ProviderMock store={store1}>
+          <ProviderMock context={customContext} store={store2}>
+            <A />
+          </ProviderMock>
+        </ProviderMock>
+      )
       expect(mapStateToPropsB).toHaveBeenCalledTimes(1)
       expect(mapStateToPropsC).toHaveBeenCalledTimes(1)
       expect(mapStateToPropsD).toHaveBeenCalledTimes(1)
@@ -2357,18 +2518,17 @@ describe('React', () => {
       expect(mapStateToPropsD).toHaveBeenCalledTimes(2)
     })
 
-
-    it.skip('works in <StrictMode> without warnings (React 16.3+)', () => {
+    it('works in <StrictMode> without warnings (React 16.3+)', () => {
       if (!React.StrictMode) {
         return
       }
       const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
       const store = createStore(stringBuilder)
 
-      @connect(state => ({ string: state }) )
+      @connect(state => ({ string: state }))
       class Container extends Component {
         render() {
-          return <Passthrough {...this.props}/>
+          return <Passthrough {...this.props} />
         }
       }
 
@@ -2383,25 +2543,66 @@ describe('React', () => {
       expect(spy).not.toHaveBeenCalled()
     })
 
-    it('should receive the store in the context using a custom store key', () => {
-      const store = createStore(() => ({}))
-      const CustomProvider = createProvider('customStoreKey')
-      const connectOptions = { storeKey: 'customStoreKey' }
-
-      @connect(undefined, undefined, undefined, connectOptions)
+    it('should error on withRef=true', () => {
       class Container extends Component {
         render() {
-          return <Passthrough {...this.props} />
+          return <div>hi</div>
         }
       }
+      expect(() =>
+        connect(
+          undefined,
+          undefined,
+          undefined,
+          { withRef: true }
+        )(Container)
+      ).toThrow(/withRef is removed/)
+    })
 
-      const tester = rtl.render(
-        <CustomProvider store={store}>
-          <Container />
-        </CustomProvider>
-      )
+    it('should error on receiving a custom store key', () => {
+      const connectOptions = { storeKey: 'customStoreKey' }
+
+      expect(() => {
+        @connect(
+          undefined,
+          undefined,
+          undefined,
+          connectOptions
+        )
+        class Container extends Component {
+          render() {
+            return <Passthrough {...this.props} />
+          }
+        }
+        new Container()
+      }).toThrow(/storeKey has been removed/)
+    })
+
+    it('should error on custom store', () => {
+      function Comp() {
+        return <div>hi</div>
+      }
+      const Container = connect()(Comp)
+      function Oops() {
+        return <Container store={'oops'} />
+      }
+      expect(() => {
+        rtl.render(<Oops />)
+      }).toThrow(/Passing redux store/)
+    })
 
-      expect(tester.getByTestId('dispatch')).toHaveTextContent('[function dispatch]')
+    it('should error on renderCount prop if specified in connect options', () => {
+      function Comp(props) {
+        return <div>{props.count}</div>
+      }
+      expect(() => {
+        connect(
+          undefined,
+          undefined,
+          undefined,
+          { renderCountProp: 'count' }
+        )(Comp)
+      }).toThrow(/renderCountProp is removed/)
     })
   })
 })