From a2d09c0ee1201c1fb8b091c5edacd4ecf78a1f7e Mon Sep 17 00:00:00 2001 From: Mo Kouli Date: Tue, 10 Apr 2018 14:34:30 -0700 Subject: [PATCH] Support `React.createContext()` https://github.com/fusionjs/fusion-react-async/pull/90 --- README.md | 2 +- package.json | 12 ++-- src/__tests__/__node__/prepare-render.node.js | 67 +++++++++++++++++++ src/prepare.js | 8 ++- yarn.lock | 16 +++-- 5 files changed, 90 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 85735ef..6b28c1b 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ const hoc = prepared(sideEffect, opts); ``` * `sideEffect: : (props: Object, context: Object) => Promise` - Required. when `prepare` is called, `sideEffect` is called (and awaited) before continuing the rendering traversal. -* `opts: {defer, boundary, componentDidMount, componentWillReceiveProps, forceUpdate, contextTypes}` - Optional +* `opts: {defer, boundary, componentDidMount, componentWillReceiveProps, componentDidUpdate, forceUpdate, contextTypes}` - Optional * `defer: boolean` - Optional. Defaults to `true`. If the component is deferred, skip the prepare step * `boundary: boolean` - Optional. Defaults to `false`. Stop traversing if the component is defer or boundary * `componentDidMount: boolean` - Optional. Defaults to `true`. On the browser, `sideEffect` is called when the component is mounted. diff --git a/package.json b/package.json index bb597c3..db3e087 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,7 @@ "version": "1.2.0", "description": "Prepare you app state for async rendering", "repository": "fusionjs/fusion-react-async", - "files": [ - "dist", - "src" - ], + "files": ["dist", "src"], "main": "./dist/index.js", "module": "./dist/index.es.js", "browser": { @@ -30,7 +27,8 @@ "test": "npm run build-test && npm run just-test" }, "dependencies": { - "prop-types": "^15.5.8" + "prop-types": "^15.5.8", + "react-is": "^16.3.1" }, "devDependencies": { "@babel/preset-react": "7.0.0-beta.40", @@ -48,8 +46,8 @@ "flow-bin": "0.66.0", "nyc": "11.4.1", "prettier": "1.11.1", - "react": "16.2.0", - "react-dom": "16.2.0", + "react": "16.3.0", + "react-dom": "16.3.0", "tape-cup": "4.7.1" }, "peerDependencies": { diff --git a/src/__tests__/__node__/prepare-render.node.js b/src/__tests__/__node__/prepare-render.node.js index 046073c..a340238 100644 --- a/src/__tests__/__node__/prepare-render.node.js +++ b/src/__tests__/__node__/prepare-render.node.js @@ -489,3 +489,70 @@ tape('Preparing a fragment with async children', t => { t.end(); }); }); + +tape('Preparing React.createContext()', t => { + const {Provider, Consumer} = React.createContext('light'); + + const app = ( + + 1 + + 2 + + + ); + const p = prepare(app); + t.ok(p instanceof Promise, 'prepare returns a promise'); + p.then(() => { + const wrapper = shallow(
{app}
); + t.equal(wrapper.find('span').length, 2, 'has two children'); + t.end(); + }); +}); + +tape('Preparing React.createContext() with async children', t => { + const {Provider, Consumer} = React.createContext('light'); + + let numChildRenders = 0; + let numPrepares = 0; + function SimplePresentational() { + numChildRenders++; + + return ( + + {theme => { + return
{theme}
; + }} +
+ ); + } + const AsyncChild = prepared(props => { + numPrepares++; + t.equal( + props.data, + 'test', + 'passes props through to prepared component correctly' + ); + return Promise.resolve(); + })(SimplePresentational); + + const app = ( + + + + + ); + const p = prepare(app); + t.ok(p instanceof Promise, 'prepare returns a promise'); + p.then(() => { + t.equal(numPrepares, 2, 'runs prepare function twice'); + t.equal(numChildRenders, 2, 'renders SimplePresentational twice'); + + t.equal( + shallow(
{app}
).html(), + '
dark
dark
', + 'passes values via context' + ); + t.end(); + }); +}); diff --git a/src/prepare.js b/src/prepare.js index ff89423..eef617d 100644 --- a/src/prepare.js +++ b/src/prepare.js @@ -5,6 +5,7 @@ */ import React from 'react'; +import {isFragment, isContextConsumer, isContextProvider} from 'react-is'; import isReactCompositeComponent from './utils/isReactCompositeComponent'; import {isPrepared, getPrepare} from './prepared'; @@ -41,7 +42,12 @@ function prepareElement(element, context) { return Promise.resolve([null, context]); } const {type, props} = element; - if (typeof type === 'string' || type === React.Fragment) { + if ( + typeof type === 'string' || + isFragment(element) || + isContextConsumer(element) || + isContextProvider(element) + ) { return Promise.resolve([props.children, context]); } if (!isReactCompositeComponent(type)) { diff --git a/yarn.lock b/yarn.lock index a5de9cb..9c89378 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3724,15 +3724,19 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" +react-dom@16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.0.tgz#b318e52184188ecb5c3e81117420cca40618643e" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.0" +react-is@^16.3.1: + version "16.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.1.tgz#ee66e6d8283224a83b3030e110056798488359ba" + react-reconciler@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d" @@ -3750,9 +3754,9 @@ react-test-renderer@^16.0.0-0: object-assign "^4.1.1" prop-types "^15.6.0" -react@16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" +react@16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.3.0.tgz#fc5a01c68f91e9b38e92cf83f7b795ebdca8ddff" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0"