Skip to content

Commit

Permalink
feat(innercomponent): Manage inner component changing size detection
Browse files Browse the repository at this point in the history
This new feature allow to watch inner component changing (new state, props) and notice the
SizeFetcher
  • Loading branch information
Luc Merceron committed Apr 30, 2017
1 parent cfc62fe commit e14af63
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 13 deletions.
34 changes: 34 additions & 0 deletions src/EnhanceInnerComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'

const getDisplayName = wrappedComponent => wrappedComponent.displayName || wrappedComponent.name

const EnhanceInnerComponent = InnerComponent => {
// const component = InnerComponent

// const NewInnerComponent = class extends React.Component {
// render() {
// return component(this.props)
// }
// }
// NewInnerComponent.displayName = getDisplayName(component)

class EnhancerInnerComponent extends InnerComponent {
componentDidMount() {
if (super.componentDidMount) super.componentDidMount()
}
componentDidUpdate() {
if (super.componentDidUpdate) super.componentDidUpdate()

this.props.sizeMayChange()
}
render() {
return super.render()
}
}

EnhancerInnerComponent.displayName = `EnhancerInner(${getDisplayName(InnerComponent)})`

return EnhancerInnerComponent
}

export default EnhanceInnerComponent
58 changes: 47 additions & 11 deletions src/SizeFetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'

import warning from './utils/warning'
import EnhanceInnerComponent from './EnhanceInnerComponent'

const getDisplayName = wrappedComponent => wrappedComponent.displayName || wrappedComponent.name
const isStateless = component => !component.render && !(component.prototype && component.prototype.render)
Expand Down Expand Up @@ -30,6 +31,16 @@ const SizeFetcher = (SubComponent, options = { noComparison: false }) => {
}

class Enhancer extends ComposedComponent {
constructor() {
super()

this.elementsTree = super.render()
this.enhancedChildren = this.privateEnhanceChildren(this.elementsTree.props.children)

this.privateHandleSizeMayHaveChanged = this.privateHandleSizeMayHaveChanged.bind(this)
this.privateSizeChanged = this.privateSizeChanged.bind(this)
this.privateRegisterComponentInfos = this.privateRegisterComponentInfos.bind(this)
}
componentDidMount() {
if (super.componentDidMount) super.componentDidMount()
const { clientHeight, clientWidth, scrollHeight, scrollWidth } = this.comp
Expand All @@ -53,9 +64,9 @@ const SizeFetcher = (SubComponent, options = { noComparison: false }) => {
const { sizeChange } = this.props

// First call of the callback, the component mounted and we need to give its size
sizeChange({ clientHeight: clientHeight, clientWidth: clientWidth, scrollHeight: scrollHeight, scrollWidth: scrollWidth })
sizeChange({ clientHeight, clientWidth, scrollHeight, scrollWidth })
// Register the dimension for future sake (comparison)
this.privateRegisterComponentInfos()
this.privateRegisterComponentInfos(clientHeight, clientWidth, scrollHeight, scrollWidth)
}
privateHandleSizeMayHaveChanged() {
const { clientHeight, clientWidth, scrollHeight, scrollWidth } = this.comp
Expand All @@ -66,25 +77,50 @@ const SizeFetcher = (SubComponent, options = { noComparison: false }) => {
this.privateSizeChanged(clientHeight, clientWidth, scrollHeight, scrollWidth)
}
}
privateRegisterComponentInfos() {
const { clientHeight, clientWidth, scrollHeight, scrollWidth } = this.comp

privateRegisterComponentInfos(clientHeight, clientWidth, scrollHeight, scrollWidth) {
// Register the height & width so we can compare them in the future
this.clientHeight = clientHeight
this.clientWidth = clientWidth
this.scrollHeight = scrollHeight
this.scrollWidth = scrollWidth
}
privateEnhanceChildren(child) {
let children
if (child && child.props && Object.prototype.hasOwnProperty.call(child.props, 'children')) children = child.props.children

// Zero case: the child is multiple
if (child && Array.isArray(child)) {
return child.map(ch => this.privateEnhanceChildren(ch))
} else if (children && Array.isArray(children)) {
// First case: the children is composed of multiple child
return Object.assign({}, child, {
props: Object.assign({}, child.props, {
children: React.Children.map(children, ch => this.privateEnhanceChildren(ch)),
}),
})
} else if (children && children instanceof Object) {
// Second case: The children is the only one
return Object.assign({}, child, {
props: Object.assign({}, child.props, {
children: this.privateEnhanceChildren(children),
}),
})
} else if (child && typeof child.type === 'function') {
// Third case: The children is actually an innerComponent
const EnhancedInner = EnhanceInnerComponent(child.type)
const display = <EnhancedInner {...child.props} sizeMayChange={() => this.privateHandleSizeMayHaveChanged()} />
// console.log('-------', display, child)
return display
}
// No enhancement
return child
}

render() {
// Retrieve the component render tree
const elementsTree = super.render()

// Here thanks to II, we can add a ref without the subComponent noticing
let newProps = { ref: comp => (this.comp = comp) }

const newProps = { ref: comp => (this.comp = comp) }
// Create a new component from SubComponent render with new props
const newElementsTree = React.cloneElement(elementsTree, newProps, elementsTree.props.children)
const newElementsTree = React.cloneElement(this.elementsTree, newProps, this.enhancedChildren)
return newElementsTree
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe('SizeFetcher component', () => {
const initialProps = { test: 'ok' }
class InnnerComponent extends React.Component {
componentDidMount() {
console.log('OYOYO')
this.forceUpdate()
}
render() {
Expand Down Expand Up @@ -53,7 +52,7 @@ describe('SizeFetcher component', () => {
}
const InnerReactComponent = () => (<div>
InnerComponent :
{InnnerComponent}
<InnnerComponent />
</div>)

const EnhancedFunctionalComponent = SizeFetcher(FunctionalReactComponent, { noComparison: true })
Expand Down Expand Up @@ -108,6 +107,7 @@ describe('SizeFetcher component', () => {
})
it('should call sizeChange function when an innerComponent update itseft', () => {
const WrapperEnhancedInnerComponent = mount(<EnhancedInnerComponent sizeChange={sizeChangeThird}/>)
console.log(WrapperEnhancedInnerComponent.html())
// First call at mount then when sub component force its update
expect(sizeChangeThird.mock.calls.length).toEqual(2)
})
Expand Down

0 comments on commit e14af63

Please sign in to comment.