Skip to content

Commit

Permalink
fix(package): do not access window/document in ssr (#806)
Browse files Browse the repository at this point in the history
* feat(isBrowser): add browser check util

* fix(Portal): avoid window/document in ssr

* fix(Dimmer): avoid window/document in ssr

* fix(Modal): avoid window/document in ssr

* fix(Popup): avoid window/document in ssr

* fix(Dropdown): avoid window/document in ssr

* fix(Search): avoid window/document in ssr
  • Loading branch information
levithomason authored Nov 10, 2016
1 parent 436e856 commit cb3f761
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 22 deletions.
11 changes: 8 additions & 3 deletions src/addons/Portal/Portal.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AutoControlledComponent as Component,
customPropTypes,
keyboardKey,
isBrowser,
makeDebugger,
META,
} from '../../lib'
Expand Down Expand Up @@ -112,6 +113,7 @@ class Portal extends Component {
closeOnDocumentClick: true,
closeOnEscape: true,
openOnTriggerClick: true,
mountNode: isBrowser ? document.body : null,
}

static autoControlledProps = [
Expand Down Expand Up @@ -322,6 +324,9 @@ class Portal extends Component {

this.mountPortal()

// Server side rendering
if (!isBrowser) return null

this.node.className = className || ''

ReactDOM.unstable_renderSubtreeIntoContainer(
Expand All @@ -337,11 +342,11 @@ class Portal extends Component {
}

mountPortal = () => {
if (this.node) return
if (!isBrowser || this.node) return

debug('mountPortal()')

const { mountNode = document.body, prepend } = this.props
const { mountNode, prepend } = this.props

this.node = document.createElement('div')

Expand All @@ -359,7 +364,7 @@ class Portal extends Component {
}

unmountPortal = () => {
if (!this.node) return
if (!isBrowser || !this.node) return

debug('unmountPortal()')

Expand Down
1 change: 1 addition & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
export * from './factories'
export { default as getUnhandledProps } from './getUnhandledProps'
export { default as getElementType } from './getElementType'
export { default as isBrowser } from './isBrowser'
export * as META from './META'
export * as SUI from './SUI'

Expand Down
4 changes: 4 additions & 0 deletions src/lib/isBrowser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const hasDocument = typeof document === 'object'
const hasWindow = typeof window === 'object' || window.self === window

export default hasDocument && hasWindow
19 changes: 12 additions & 7 deletions src/modules/Dimmer/Dimmer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
customPropTypes,
getElementType,
getUnhandledProps,
isBrowser,
META,
useKeyOnly,
} from '../../lib'
Expand Down Expand Up @@ -60,9 +61,13 @@ export default class Dimmer extends Component {

static Dimmable = DimmerDimmable

handlePortalMount = () => document.body.classList.add('dimmed', 'dimmable')
handlePortalMount = () => {
if (isBrowser) document.body.classList.add('dimmed', 'dimmable')
}

handlePortalUnmount = () => document.body.classList.remove('dimmed', 'dimmable')
handlePortalUnmount = () => {
if (isBrowser) document.body.classList.remove('dimmed', 'dimmable')
}

handleClick = (e) => {
const { onClick, onClickOutside } = this.props
Expand Down Expand Up @@ -98,12 +103,12 @@ export default class Dimmer extends Component {
const ElementType = getElementType(Dimmer, this.props)

const childrenJSX = (children || content) && (
<div className='content'>
<div className='center' ref={center => (this.center = center)}>
{ children || content }
<div className='content'>
<div className='center' ref={center => (this.center = center)}>
{ children || content }
</div>
</div>
</div>
)
)

if (page) {
return (
Expand Down
14 changes: 14 additions & 0 deletions src/modules/Dropdown/Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
customPropTypes,
getElementType,
getUnhandledProps,
isBrowser,
keyboardKey,
makeDebugger,
META,
Expand Down Expand Up @@ -338,6 +339,9 @@ export default class Dropdown extends Component {
// TODO objectDiff still runs in prod, stop it
debug('to state:', objectDiff(prevState, this.state))

// Do not access document when server side rendering
if (!isBrowser) return

// focused / blurred
if (!prevState.focus && this.state.focus) {
debug('dropdown focused')
Expand Down Expand Up @@ -389,6 +393,10 @@ export default class Dropdown extends Component {

componentWillUnmount() {
debug('componentWillUnmount()')

// Do not access document when server side rendering
if (!isBrowser) return

document.removeEventListener('keydown', this.openOnArrow)
document.removeEventListener('keydown', this.openOnSpace)
document.removeEventListener('keydown', this.moveSelectionOnKeyDown)
Expand Down Expand Up @@ -531,12 +539,16 @@ export default class Dropdown extends Component {
const { onMouseDown } = this.props
if (onMouseDown) onMouseDown(e)
this.isMouseDown = true
// Do not access document when server side rendering
if (!isBrowser) return
document.addEventListener('mouseup', this.handleDocumentMouseUp)
}

handleDocumentMouseUp = () => {
debug('handleDocumentMouseUp()')
this.isMouseDown = false
// Do not access document when server side rendering
if (!isBrowser) return
document.removeEventListener('mouseup', this.handleDocumentMouseUp)
}

Expand Down Expand Up @@ -804,6 +816,8 @@ export default class Dropdown extends Component {

scrollSelectedItemIntoView = () => {
debug('scrollSelectedItemIntoView()')
// Do not access document when server side rendering
if (!isBrowser) return
const menu = document.querySelector('.ui.dropdown.active.visible .menu.visible')
const item = menu.querySelector('.item.selected')
debug(`menu: ${menu}`)
Expand Down
22 changes: 12 additions & 10 deletions src/modules/Modal/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
customPropTypes,
getElementType,
getUnhandledProps,
isBrowser,
makeDebugger,
META,
useKeyOnly,
Expand Down Expand Up @@ -79,6 +80,8 @@ class Modal extends Component {

static defaultProps = {
dimmer: true,
// Do not access document when server side rendering
mountNode: isBrowser ? document.body : null,
}

static _meta = _meta
Expand Down Expand Up @@ -106,8 +109,7 @@ class Modal extends Component {

handlePortalMount = (e) => {
debug('handlePortalMount()')
const { dimmer } = this.props
const mountNode = this.getMountNode()
const { dimmer, mountNode } = this.props

if (dimmer) {
debug('adding dimmer')
Expand All @@ -131,7 +133,7 @@ class Modal extends Component {
// Always remove all dimmer classes.
// If the dimmer value changes while the modal is open, then removing its
// current value could leave cruft classes previously added.
const mountNode = this.getMountNode()
const { mountNode } = this.props
mountNode.classList.remove('blurring', 'dimmable', 'dimmed', 'scrollable')

cancelAnimationFrame(this.animationRequestId)
Expand All @@ -140,13 +142,9 @@ class Modal extends Component {
if (onUnmount) onUnmount(e, this.props)
}

getMountNode = () => {
return this.props.mountNode || document.body
}

setPosition = () => {
if (this._modalNode) {
const mountNode = this.getMountNode()
const { mountNode } = this.props
const { height } = this._modalNode.getBoundingClientRect()
const scrolling = height >= window.innerHeight

Expand All @@ -171,7 +169,11 @@ class Modal extends Component {
}

render() {
const { basic, children, className, dimmer, size } = this.props
const { basic, children, className, dimmer, mountNode, size } = this.props

// Short circuit when server side rendering
if (!isBrowser) return null

const { marginTop, scrolling } = this.state
const classes = cx(
'ui',
Expand Down Expand Up @@ -218,7 +220,7 @@ class Modal extends Component {
closeOnDocumentClick={false}
{...portalProps}
className={dimmerClasses}
mountNode={this.getMountNode()}
mountNode={mountNode}
onClose={this.handleClose}
onMount={this.handlePortalMount}
onOpen={this.handleOpen}
Expand Down
5 changes: 5 additions & 0 deletions src/modules/Popup/Popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import _ from 'lodash'
import {
getElementType,
getUnhandledProps,
isBrowser,
META,
SUI,
useKeyOnly,
Expand Down Expand Up @@ -117,6 +118,10 @@ export default class Popup extends Component {

computePopupStyle(positions) {
const style = { position: 'absolute' }

// Do not access window/document when server side rendering
if (!isBrowser) return style

const { offset } = this.props
const { pageYOffset, pageXOffset } = window
const { clientWidth, clientHeight } = document.documentElement
Expand Down
18 changes: 16 additions & 2 deletions src/modules/Search/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
customPropTypes,
getElementType,
getUnhandledProps,
isBrowser,
keyboardKey,
makeDebugger,
META,
Expand Down Expand Up @@ -191,6 +192,9 @@ export default class Search extends Component {
// TODO objectDiff still runs in prod, stop it
debug('to state:', objectDiff(prevState, this.state))

// Do not access document when server side rendering
if (!isBrowser) return

// focused / blurred
if (!prevState.focus && this.state.focus) {
debug('search focused')
Expand Down Expand Up @@ -232,6 +236,10 @@ export default class Search extends Component {

componentWillUnmount() {
debug('componentWillUnmount()')

// Do not access document when server side rendering
if (!isBrowser) return

document.removeEventListener('keydown', this.moveSelectionOnKeyDown)
document.removeEventListener('keydown', this.selectItemOnEnter)
document.removeEventListener('keydown', this.closeOnEscape)
Expand Down Expand Up @@ -306,12 +314,16 @@ export default class Search extends Component {
const { onMouseDown } = this.props
if (onMouseDown) onMouseDown(e)
this.isMouseDown = true
// Do not access document when server side rendering
if (!isBrowser) return
document.addEventListener('mouseup', this.handleDocumentMouseUp)
}

handleDocumentMouseUp = () => {
debug('handleDocumentMouseUp()')
this.isMouseDown = false
// Do not access document when server side rendering
if (!isBrowser) return
document.removeEventListener('mouseup', this.handleDocumentMouseUp)
}

Expand Down Expand Up @@ -431,6 +443,8 @@ export default class Search extends Component {

scrollSelectedItemIntoView = () => {
debug('scrollSelectedItemIntoView()')
// Do not access document when server side rendering
if (!isBrowser) return
const menu = document.querySelector('.ui.search.active.visible .results.visible')
const item = menu.querySelector('.result.active')
debug(`menu (results): ${menu}`)
Expand Down Expand Up @@ -493,9 +507,9 @@ export default class Search extends Component {
return (
<div className='message empty'>
<div className='header'>{noResultsMessage}</div>
{noResultsDescription &&
{noResultsDescription && (
<div className='description'>{noResultsDescription}</div>
}
)}
</div>
)
}
Expand Down

0 comments on commit cb3f761

Please sign in to comment.