lancetw/react-isomorphic-bundle

View on GitHub
src/shared/components/wall/PostCards.js

Summary

Maintainability
B
4 hrs
Test Coverage
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import { isEmpty, debounce } from 'lodash'
import Card from 'shared/components/wall/PostCard'
import classNames from 'classnames'
import shouldPureComponentUpdate from 'react-pure-render/function'
import { detectIE } from 'shared/utils/browser-utils'

let $
let ReactList

if (process.env.BROWSER) {
  ReactList = require('react-list')
  $ = require('jquery')
  require('css/ui/spinkit')

  // requestAnimationFrame: http://www.inazumatv.com/contents/archives/9226
  let lastTime = 0
  const vendors = ['ms', 'moz', 'webkit', 'o']
  for (let x = 0, limit = vendors.length; x < limit && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']
  }

  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = function (callback) {
      const currTime = new Date().getTime()
      const timeToCall = Math.max(0, 16 - (currTime - lastTime))
      const id = window.setTimeout(function () { callback( currTime + timeToCall ); }, timeToCall)
      lastTime = currTime + timeToCall
      return id
    }
  }

  if (!window.cancelAnimationFrame) {
    window.cancelAnimationFrame = function (id) {
      clearTimeout(id)
    }
  }
}

export default class PostCards extends Component {

  static propTypes = {
    posts: PropTypes.array.isRequired,
    loadFunc: PropTypes.func.isRequired,
    hasMore: PropTypes.bool.isRequired,
    diff: PropTypes.number.isRequired,
    isFetching: PropTypes.bool.isRequired,
    threshold: PropTypes.number
  }

  static defaultProps = {
    diff: 0,
    isFetching: false,
    threshold: 540
  }

  constructor (props) {
    super(props)
    this.state = {
      triggered: false,
      windowWidth: typeof window !== 'undefined' && window.innerWidth,
      windowHeight: typeof window !== 'undefined' && window.innerHeight
    }

    this.scrollTimeout = null
    this.lastPosition = -1
    this.rafId = null
  }

  componentDidMount () {
    window.addEventListener('resize', this.handleResize)
    this.fastScrollLoop()
  }

  componentWillReceiveProps (nextProps) {
    if (!nextProps.isFetching) {
      this.scrollTimeout = setTimeout(() => {
        this.setState({ triggered: false })
        $('#overlay').remove()
      }, 0)
    }
  }

  shouldComponentUpdate = shouldPureComponentUpdate

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleResize)
    if (this.op) {
      clearTimeout(this.scrollTimeout)
    }
    this.lastPosition = -1
    window.cancelAnimationFrame(this.rafId)
  }

  handleResize = () => {
    this.setState({
      windowWidth: window.innerWidth, windowHeight: window.innerHeight
    })
  }

  handleScroll = (event) => {
    if (!!this.props.hasMore) {
      const nodeScroll = ReactDOM.findDOMNode(this.refs.scroll)
      this.handleInfiniteLoad(nodeScroll.scrollTop)
    }
  }

  handleInfiniteLoad = (scrollTop) => {
    const nodeScroll = ReactDOM.findDOMNode(this.refs.scroll)
    const nodeList = ReactDOM.findDOMNode(this.refs.scrollList)
    const contentHeight = $(nodeList).height() - $(nodeScroll).height()
    const loading = this.props.isFetching
    const checkPoint = (contentHeight - this.props.threshold) > 0
      ? contentHeight - this.props.threshold
      : contentHeight

    /* eslint-disable max-len */
    if (!this.state.triggered && scrollTop > checkPoint) {
      $('<div id="overlay"><div id="loading"><div class="scroll-spinner"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div></div></div>').appendTo('.ui.scrollable')
      this.setState({ triggered: true })

      this.props.loadFunc()
    }
  }

  fastScrollLoop = () => {
    const nodeScroll = ReactDOM.findDOMNode(this.refs.scroll)

    if (nodeScroll) {
      const scrollTop = nodeScroll.scrollTop

      if (this.lastPosition === scrollTop) {
        this.rafId = window.requestAnimationFrame(this.fastScrollLoop)
        return false
      } else {
        this.lastPosition = scrollTop
      }

      if (!!this.props.hasMore) {
        this.handleInfiniteLoad(scrollTop)
      }
    }

    this.rafId = window.requestAnimationFrame(this.fastScrollLoop)
  }

  renderItem = (index, key) => {
    const card = this.props.posts[index]
    return (
      <Card
        key={key}
        data={card}
      />
    )
  }

  renderScrollList = (type, useTranslate3d) => {
    return (
      <ReactList
        ref="scrollList"
        threshold={60}
        pageSize={240}
        initialIndex={0}
        itemRenderer={this.renderItem}
        length={this.props.posts.length}
        type={type}
        useTranslate3d={useTranslate3d} />
    )
  }

  render () {
    const cards = this.props.posts

    const scrollClass = classNames(
      'ui',
      'scrollable'
    )

    if (process.env.BROWSER && cards.length > 0) {
      const containerHeight = this.state.windowHeight - this.props.diff
      const isIE = detectIE()
      if (!isIE || isIE > 11) {
        const useTranslate3d = true
        return (
          <div
            className={scrollClass}
            ref="scroll"
            style={{
              maxHeight: containerHeight
            }}>
            {this.renderScrollList('variable', useTranslate3d)}
          </div>
        )
      } else {
        const useTranslate3d = false
        return (
          <div
            className={scrollClass}
            ref="scroll"
            style={{
              maxHeight: containerHeight
            }}>
            {this.renderScrollList('variable', useTranslate3d)}
          </div>
        )
      }
    } else {
      return (
        <div className="ui cards" ref="scrollable">
          {!isEmpty(cards) && cards.map(function (card) {
            return <Card key={card.id} data={card} />
          })}
        </div>
      )
    }
  }
}