Skip to content

Commit

Permalink
Merge pull request #977 from gaearon/update-related-issues
Browse files Browse the repository at this point in the history
Update related issues
  • Loading branch information
theKashey authored May 16, 2018
2 parents 0584e27 + 56fdd05 commit 769eb72
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 74 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ Set a new configuration for React Hot Loader.
Available options are:

* `logLevel`: specify log level, default to `"error"`, available values are: `['debug', 'log', 'warn', 'error']`
* `pureSFC`: enable Stateless Functional Component. If disabled they will be converted to React Components.
Default value: false.

```js
import { setConfig } from 'react-hot-loader'
Expand Down
1 change: 1 addition & 0 deletions src/configuration.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const configuration = {
logLevel: 'error',
pureSFC: false,
}

export default configuration
12 changes: 12 additions & 0 deletions src/internal/reactUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ export const updateInstance = instance => {

export const isFragmentNode = ({ type }) =>
React.Fragment && type === React.Fragment

const ContextType = React.createContext ? React.createContext() : null
const ConsumerType = ContextType && ContextType.Consumer.$$typeof
const ProviderType = ContextType && ContextType.Provider.$$typeof

export const CONTEXT_CURRENT_VALUE = '_currentValue'

export const isContextConsumer = ({ type }) =>
type && typeof type === 'object' && type.$$typeof === ConsumerType
export const isContextProvider = ({ type }) =>
type && typeof type === 'object' && type.$$typeof === ProviderType
export const getContextProvider = type => type && type._context
13 changes: 8 additions & 5 deletions src/proxy/createClassProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
proxyClassCreator,
} from './utils'
import { inject, checkLifeCycleMethods, mergeComponents } from './inject'
import config from '../configuration'

const has = Object.prototype.hasOwnProperty

Expand Down Expand Up @@ -239,12 +240,14 @@ function createClassProxy(InitialComponent, proxyKey, options) {
const result = CurrentComponent(props, context)

// simple SFC
if (!CurrentComponent.contextTypes) {
if (!ProxyFacade.isStatelessFunctionalProxy) {
setSFPFlag(ProxyFacade, true)
}
if (config.pureSFC) {
if (!CurrentComponent.contextTypes) {
if (!ProxyFacade.isStatelessFunctionalProxy) {
setSFPFlag(ProxyFacade, true)
}

return renderOptions.componentDidRender(result)
return renderOptions.componentDidRender(result)
}
}
setSFPFlag(ProxyFacade, false)

Expand Down
63 changes: 59 additions & 4 deletions src/reconciler/hotReplacementRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {
updateInstance,
getComponentDisplayName,
isFragmentNode,
isContextConsumer,
isContextProvider,
getContextProvider,
CONTEXT_CURRENT_VALUE,
} from '../internal/reactUtils'
import reactHotLoader from '../reactHotLoader'
import logger from '../logger'
Expand All @@ -22,6 +26,10 @@ const stackReport = () => {
logger.warn('in', rev[0].name, rev)
}

const emptyMap = new Map()
const stackContext = () =>
(renderStack[renderStack.length - 1] || {}).context || emptyMap

const areNamesEqual = (a, b) =>
a === b || (UNDEFINED_NAMES[a] && UNDEFINED_NAMES[b])
const isReactClass = fn => fn && !!fn.render
Expand Down Expand Up @@ -214,8 +222,13 @@ const mergeInject = (a, b, instance) => {

const transformFlowNode = flow =>
flow.reduce((acc, node) => {
if (isFragmentNode(node) && node.props && node.props.children) {
return [...acc, ...filterNullArray(asArray(node.props.children))]
if (node && isFragmentNode(node)) {
if (node.props && node.props.children) {
return [...acc, ...filterNullArray(asArray(node.props.children))]
}
if (node.children) {
return [...acc, ...filterNullArray(asArray(node.children))]
}
}
return [...acc, node]
}, [])
Expand Down Expand Up @@ -246,10 +259,12 @@ const scheduleInstanceUpdate = instance => {
const hotReplacementRender = (instance, stack) => {
if (isReactClass(instance)) {
const type = getElementType(stack)

renderStack.push({
name: getComponentDisplayName(type),
type,
props: stack.instance.props,
context: stackContext(),
})
}
const flow = transformFlowNode(filterNullArray(asArray(render(instance))))
Expand Down Expand Up @@ -279,6 +294,15 @@ const hotReplacementRender = (instance, stack) => {

// text node
if (typeof child !== 'object' || !stackChild || !stackChild.instance) {
if (stackChild && stackChild.children && stackChild.children.length) {
logger.error(
'React-hot-loader: reconciliation failed',
'could not dive into [',
child,
'] while some elements are still present in the tree.',
)
stackReport()
}
return
}

Expand All @@ -296,15 +320,46 @@ const hotReplacementRender = (instance, stack) => {
return
}

if (typeof child.type !== 'function') {
// React context
if (isContextConsumer(child)) {
try {
next({
children: (child.props ? child.props.children : child.children[0])(
stackContext().get(child.type) || child.type[CONTEXT_CURRENT_VALUE],
),
})
} catch (e) {
// do nothing, yet
}
} else if (typeof child.type !== 'function') {
// React
let childName = child.type ? getComponentDisplayName(child.type) : 'empty'
let extraContext = stackContext()

if (isContextProvider(child)) {
extraContext = new Map(extraContext)
extraContext.set(getContextProvider(child.type), child.props.value)
childName = 'ContextProvider'
}

renderStack.push({
name: childName,
type: child.type,
props: stack.instance.props,
context: extraContext,
})

next(
// move types from render to the instances of hydrated tree
mergeInject(
asArray(child.props ? child.props.children : child.children),
transformFlowNode(
asArray(child.props ? child.props.children : child.children),
),
stackChild.instance.children,
stackChild.instance,
),
)
renderStack.pop()
} else {
// unwrap proxy
const childType = getElementType(child)
Expand Down
18 changes: 15 additions & 3 deletions test/AppContainer.dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1970,7 +1970,13 @@ describe(`AppContainer (dev)`, () => {
const App = () => (
<React.Fragment>
<button>
text <InnerComponent />
<React.Fragment>
<span />
<span>
text <InnerComponent />
</span>
<span />
</React.Fragment>
</button>
</React.Fragment>
)
Expand Down Expand Up @@ -1998,7 +2004,13 @@ describe(`AppContainer (dev)`, () => {
const App = () => (
<React.Fragment>
<button>
another text<InnerComponent />
<React.Fragment>
<span />
<span>
another text <InnerComponent />
</span>
<span />
</React.Fragment>
</button>
</React.Fragment>
)
Expand All @@ -2008,7 +2020,7 @@ describe(`AppContainer (dev)`, () => {
}

expect(unmount).toHaveBeenCalledTimes(0)
expect(wrapper.update().text()).toBe('another textinternal')
expect(wrapper.update().text()).toBe('another text internal')
} else {
// React 15 is always ok
expect(true).toBe(true)
Expand Down
25 changes: 19 additions & 6 deletions test/proxy/consistency.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React from 'react'
import { createMounter, ensureNoWarnings } from './helper'
import createProxy from '../../src/proxy'
import configuration from '../../src/configuration'

const createFixtures = () => ({
modern: {
Expand Down Expand Up @@ -349,13 +350,25 @@ describe('consistency', () => {
expect(instance instanceof App).toBe(true)
})

it('should wrap SFC by SFC', () => {
const App = () => <div />
describe('should wrap SFC by SFC', () => {
it('should wrap SFC by SFC Component', () => {
const App = () => <div />

const Proxy = createProxy(App).get()
expect('isStatelessFunctionalProxy' in Proxy).toBe(false)
mount(<Proxy />).instance()
expect(Proxy.isStatelessFunctionalProxy).toBe(true)
const Proxy = createProxy(App).get()
expect('isStatelessFunctionalProxy' in Proxy).toBe(false)
mount(<Proxy />).instance()
expect(Proxy.isStatelessFunctionalProxy).toBe(false)
})

it('should wrap SFC by SFC Pure', () => {
const App = () => <div />
configuration.pureSFC = true
const Proxy = createProxy(App).get()
expect('isStatelessFunctionalProxy' in Proxy).toBe(false)
mount(<Proxy />).instance()
configuration.pureSFC = false
expect(Proxy.isStatelessFunctionalProxy).toBe(true)
})
})

it('should wrap SFC with Context by Proxy', () => {
Expand Down
Loading

0 comments on commit 769eb72

Please sign in to comment.