Skip to content

Commit

Permalink
Merge pull request #800 from gaearon/support-fragments
Browse files Browse the repository at this point in the history
Support React.Fragments. #799
  • Loading branch information
theKashey authored Jan 19, 2018
2 parents b8414d2 + 7923d79 commit 9277095
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 4 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ const App = () => <div>Hello World!</div>

export default hot(module)(App)
```

`Hot` accepts only React Component (Stateful or Stateless), resulting the `HotExported` variant of it.
The `hot` function will setup current module to _self-accept_ itself on reload, and will __ignore__ all the changes, made for non-React components.
You may mark as much modules as you want. But `HotExportedComponent` __should be the only used export__ of a _hot_-module.
The `hot` function will setup current module to _self-accept_ itself on reload, and will **ignore** all the changes, made for non-React components.
You may mark as much modules as you want. But `HotExportedComponent` **should be the only used export** of a _hot_-module.

> Note: Please note how often we have used `exported` keyword. `hot` is for exports.
> Note: does nothing in production mode, just passes App through.
Expand Down Expand Up @@ -96,6 +98,7 @@ export default hot(module)(App)
```

### Migrating from [create-react-app](https://github.com/facebookincubator/create-react-app) without ejecting

Users [reports](https://github.com/gaearon/react-hot-loader/pull/729#issuecomment-354097936), that it is possible to use [react-app-rewire-hot-loader](https://github.com/cdharris/react-app-rewire-hot-loader) to setup React-hot-loader without ejecting.
Follow [these code examples](https://github.com/Grimones/cra-rhl/commit/4ed74af2dc649301695f67df05a12f210fb7820c) to repeat the approach.

Expand Down
4 changes: 4 additions & 0 deletions packages/react-hot-loader/src/internal/reactUtils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react'
/* eslint-disable no-underscore-dangle */

export const isCompositeComponent = type => typeof type === 'function'
Expand All @@ -18,3 +19,6 @@ export const updateInstance = instance => {
updater.enqueueForceUpdate(instance)
}
}

export const isFragmentNode = ({ type }) =>
React.Fragment && type === React.Fragment
16 changes: 14 additions & 2 deletions packages/react-hot-loader/src/reconciler/hotReplacementRender.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { PROXY_KEY, UNWRAP_PROXY } from 'react-stand-in'
import levenshtein from 'fast-levenshtein'
import { getIdByType, updateProxyById } from './proxies'
import { updateInstance, getComponentDisplayName } from '../internal/reactUtils'
import {
updateInstance,
getComponentDisplayName,
isFragmentNode,
} from '../internal/reactUtils'
import reactHotLoader from '../reactHotLoader'
import logger from '../logger'

Expand Down Expand Up @@ -156,8 +160,16 @@ const mergeInject = (a, b, instance) => {
return NO_CHILDREN
}

const transformFlowNode = flow =>
flow.reduce((acc, node) => {
if (isFragmentNode(node) && node.props && node.props.children) {
return [...acc, ...node.props.children]
}
return [...acc, node]
}, [])

const hotReplacementRender = (instance, stack) => {
const flow = filterNullArray(asArray(render(instance)))
const flow = transformFlowNode(filterNullArray(asArray(render(instance))))

const { children } = stack

Expand Down
73 changes: 73 additions & 0 deletions packages/react-hot-loader/test/AppContainer.dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1456,6 +1456,79 @@ describe(`AppContainer (dev)`, () => {
expect(wrapper.update().text()).toBe('PATCHED + 6 v2')
})

it('hot-reloads children inside Fragments', () => {
if (React.version.startsWith('16')) {
const unmount = jest.fn()
class InnerComponent extends Component {
componentWillUnmount() {
unmount()
}

render() {
return <div>OldInnerComponent</div>
}
}
InnerComponent.displayName = 'InnerComponent'

const InnerItem = () => (
<React.Fragment>
-1-<InnerComponent />
</React.Fragment>
)
RHL.register(InnerItem, 'InnerItem', 'test.js')

const Item = () => (
<React.Fragment>
<li>1</li>
<li>
<InnerItem />
</li>
<li>3</li>
</React.Fragment>
)
//
const App = () => (
<ul>
<Item />
</ul>
)

const wrapper = mount(
<AppContainer>
<App />
</AppContainer>,
)

expect(wrapper.update().text()).toBe('1-1-OldInnerComponent3')
{
class InnerComponent extends Component {
componentWillUnmount() {
unmount()
}

render() {
return <div>NewInnerComponent</div>
}
}
InnerComponent.displayName = 'InnerComponent'

const InnerItem = () => (
<React.Fragment>
-2-<InnerComponent />
</React.Fragment>
)
RHL.register(InnerItem, 'InnerItem', 'test.js')

wrapper.setProps({ children: <App /> })
}
expect(unmount).toHaveBeenCalledTimes(0)
expect(wrapper.update().text()).toBe('1-2-NewInnerComponent3')
} else {
// React 15 is always ok
expect(true).toBe(true)
}
})

it('hot-reloads children without losing state', () => {
class App extends Component {
constructor(props) {
Expand Down

0 comments on commit 9277095

Please sign in to comment.