React-virtualized: event handling delayed

Created on 25 Oct 2016  ·  16Comments  ·  Source: bvaughn/react-virtualized

When scrolling with WindowScroller, I receive this error in the browser console:

Handling of 'mousewheel' input event was delayed for 671 ms due to main thread being busy. Consider marking event handler as 'passive' to make the page more responive.

This causes the scrolling behavior to be extremely choppy and virtually unusable. Your demo content makes this package look awesome and we are attempting to convert our app from react-infinite to virtualized but are so far not having much luck.

I found this thread on the topic - and it appears that React has an open issue in regards to passive event handlers : http://stackoverflow.com/questions/39152877/consider-marking-event-handler-as-passive-to-make-the-page-more-responive

In addition to the scroll lag, it seems that the initial rendering of the component takes 1-2 seconds, and each time i resize the window it takes a couple of seconds for the UI to catch up. I dont see any of this behavior in your demos.

I will paste my code below, but I was wondering if this was a known issue, and if i'm doing anything wrong - I've been reading the documentation for days and doing my best to grock how this is supposed to work.

_renderDays() {
        var count = 0;
        return this.data.shifts.map((shiftGroup) => {
            count++;
            return (
              <Paper
                key={count}
                zDepth={2}
              >
                <Subheader>{shiftGroup.day}</Subheader>
                <mList> //materialUI List component
                  {this._renderShifts(shiftGroup.shifts)}
                </mList>
              </Paper>
            );
        });
    },
render() {
        let height = this.props.height || 400
        return (
          <WindowScroller>
            {({height, isScrolling, scrollTop})=>(
              <List
              height={height}
              rowCount={this.data.shifts.length}
              rowHeight={200}
              rowRenderer={this._renderDays}
              scrollTop={scrollTop}
              width={this.state.window.width}/>
            )}
          </WindowScroller>
        )
    },

Let me know if you need anything else to look at - these seemed to be the relevant parts of the component.

All 16 comments

When scrolling with WindowScroller, I receive this error in the browser console:

Handling of 'mousewheel' input event was delayed for 671 ms due to main thread being busy. Consider marking event handler as 'passive' to make the page more responive.

WindowScroller does not listen for a "wheel" event, only "scroll". If your application is listening for "wheel" events, you may want to try using a passive listener.

In addition to the scroll lag, it seems that the initial rendering of the component takes 1-2 seconds, and each time i resize the window it takes a couple of seconds for the UI to catch up. I dont see any of this behavior in your demos.

I assume this is because you're doing something different in your application code. It's a bit hard for me to speculate on what that may be, given the very limited information I have. I'd be happy to take a look if you would point me at your source code though.

I will paste my code below, but I was wondering if this was a known issue, and if i'm doing anything wrong - I've been reading the documentation for days and doing my best to grock how this is supposed to work.

The code you pasted above looks pretty suspicious. For example, it sets rowCount equal to this.data.shifts.length yet the rowRenderer you provide seems to render the full collection each time it's invoked. (That could be where your initial rendering performance problem is coming from for example.) This is not how react-virtualized works. You can see an example rowRenderer in the documentation here.

Given the limited context I have, I would expect your renderer to look something like this:

_renderDays({ key, index, style }) {
  const shift = this.data.shifts[index]

  return (
    <Paper
      key={key}
      style={style}
      zDepth={2}
    >
      <Subheader>{shift.day}</Subheader>
      <mList>
        {this._renderShifts(shift.shifts)}
      </mList>
    </Paper>
  )
}

That being said, I do not know why your "shift" seems to contain more "shifts" or what the second/inner _renderShifts function is used for. This could be a source of performance problems as well.

We can continue to chat on this issue. (I'll read and respond to messages.) But I'm going to close the issue at the moment as I am fairly confident that it is not a react-virtualized bug. 😄

I have same problem. But only with production env о_О
Safari works good, but other's browsers very laggy. I trying to optimize my code. If it will not work I will try post some examples.

UPD: Seems that it's problem with Yandex Metrika. @tonymckendry if you use it, try to disable webvisor

If you're using touch events, they force the browser into sync scrolling
mode which can definitely slow things down.

No idea why you'd be seeing a fast dev and slow production. That sounds the
opposite of what I'd expect.

On Oct 25, 2016 3:41 AM, "Valentin Semirulnik" [email protected]
wrote:

I have same problem. But only with production env о_О
Safari works good, but other's browsers very laggy. I trying to optimize
my code. If it will not work I will try post some examples.


You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/bvaughn/react-virtualized/issues/445#issuecomment-256000436,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABznbmUPWHjfWv6xEEUXQMuka3Ft0L2ks5q3dzKgaJpZM4KfZPW
.

@bvaughn in prod mode I enable analytics counter that caused lags.
It's not problem with react-virtualized.
Also thanks for this library! Saved a days for me.

@bvaughn those suggested changes actually got rid of all of the lag! Thanks a lot!

@tonymckendry Yay great 😄 Glad to hear!

And you're welcome, @7rulnik 😁

@bvaughn - thanks again for the help, I was chugging along, and then realized the above solution caused me to lose all reactivity in my component. I realize this slightly diverges from the topic of this original issue, but was wondering if you can suggest a solution I might explore to make the component reactive again?

My shift data is coming out of a reactive flux state but I think the fact that we are setting it to a const variable in the _renderDays function is causing it not to re-render when it changes.

@tonymckendry Check out this section of the docs. I think it should help clear things up

@bvaughn to the rescue again - turns out I needed to install the react-addons-shallow-compare package - problem solved

@bvaugn - not sure if you'd like me to open a separate question for this, but I was hoping for a little more clarification around some things with windowScroller.

With your help last week (thanks again!) I was able to get WindowScroller with list implemented across my app, it seems to work very well in the browser, but since building my app in Cordova and running it on my iphone, I've noticed some weirdness happening with my header bar. This gif shows the behavior, which is only slightly noticeable on the first screen and much more dramatic on the second:

windowscroller

For some reason, when I scroll to the top of the list, it pushes the entire view down for a second before popping it back up. I'm still wrapping my head around some of the finer points of how all of this works together (but it seems to be working awesome when I use it in the browser on my computer, and otherwise looks great on the phone besides this).

Here is the code for the 2 components I showed:

First component:

export const UnitView = React.createClass({
    displayName: "UnitView",
    propTypes: {
        height: React.PropTypes.number,
    },
    mixins: [ReactMeteorData, OnResize], 
    getMeteorData() {
        return {
            shifts: State.get(App.Constants.State.manager.unit_grouped_shifts),
            staff_and_jobs: State.get(App.Constants.State.manager.staff_jobs_used_by_shifts),
            unit: State.get(App.Constants.State.manager.selected_unit),
            preapprove: State.get(App.Constants.State.pending.drop.approve),
            showFill: State.get(App.Constants.State.notifications.showFill),
            showDetail: State.get(App.Constants.State.notifications.showDetail),
            fillJob: State.get(App.Constants.State.pending.drop.manager.fillJob),
            fillUnit: State.get(App.Constants.State.pending.drop.manager.fillUnit),
            eligibles: State.get(App.Constants.State.pending.drop.manager.eligibles),
            managerFillShift: State.get(App.Constants.State.pending.drop.manager.fillShift),
            drop: State.get(App.Constants.State.pending.drop.drop),
            loading: State.get(App.Constants.State.pending.drop.loading)
        };
    },
    componentDidUpdate(){
      this.list.forceUpdateGrid()
      this.list.recomputeRowHeights()
    },
    _renderShifts(shifts) {
      let shStatus = App.Constants.ShiftStatus
      let buttons = []
        return shifts.map((shift) => {
          switch (shift.state.status) {
            case shStatus.delivered:
              if (shift.zuus.personId == Meteor.user().profile.zuus.personId) {
                if (State.get(App.Constants.State.hasAcceptButton)) {
                  buttons = ['delivered shift accept', 'delivered shift drop', 'delivered shift swap']
                } else {
                  buttons = ['delivered shift drop', 'delivered shift swap']
                }
              } else {
                buttons = ['delivered shift fill']
              }
              break;
            case shStatus.accepted:
              //if the user is the owner of the shift
              if (shift.zuus.personId == Meteor.user().profile.zuus.personId) {
                buttons = ['delivered shift drop', 'delivered shift swap']
              } else { // if the user is the manager
                buttons = ['delivered shift fill']
              }
              break;
            case shStatus.drop.preapproval.waiting:
              buttons = ['manage fill']
              break;
            case shStatus.drop.preapproval.yes:
              buttons = ['cancel', 'manage fill']
              break;
            case shStatus.drop.approval.eligibles.selected:
              buttons = ['cancel', 'manage fill']
              break;
            case shStatus.drop.approval.eligibles.manager.yes:
              buttons = ['delivered shift fill']
              break;
            default:
          }
            return (
              <ShiftItem
                job={this.data.staff_and_jobs.jobs[shift.zuus.jobId]}
                key={shift.zuus.shiftId}
                shift={shift}
                staff={this.data.staff_and_jobs.staff[shift.zuus.personId]}
                unit={this.data.unit}
                buttons={buttons}
                swapShift={''}
                status={shift.state.status}/>
            );
        });
    },
        const shift = this.data.shifts[index]
        return (
              <Paper
                key={key}
                zDepth={2}
                style={style}
              >
                <Subheader style={{backgroundColor: '#f6f6f6', lineHeight: "25px", fontWeight: "bold", fontSize: "1.05em", padding: "10px"}}>{shift.day}</Subheader>
                <mList
                  style={{paddingBottom: "0px", "backgroundColor": "#f6f6f6"}}
                >
                  {this._renderShifts(shift.shifts)}
                </mList>
              </Paper>
            );

    },
    _getRowHeight({index}){
      let shStatus = App.Constants.ShiftStatus
      let shifts = this.data.shifts[index].shifts
      let height = 46 //height of the subheader for each day
      shifts.forEach((s)=>{
        height += 80
        if (s.notes !== undefined) {
          height += 44
        }
        switch (s.state.status) {
          //items that contain only a message
        case shStatus.accepted:
        case shStatus.drop.preapproval.no:
        case shStatus.drop.approval.assign:
        case shStatus.drop.approval.assignee_yes:
        case shStatus.drop.approval.eligibles.accepted:
        case shStatus.drop.approval.eligibles.manager.yes:
        case shStatus.drop.approval.eligibles.manager.no:
        case shStatus.drop.cancelled:
        case shStatus.swap.choose_staff:
        case shStatus.swap.swappee_yes:
        case shStatus.swap.swapper.accepted:
        case shStatus.swap.manager.yes:
        case shStatus.swap.manager.no:
        case shStatus.swap.cancelled:
          height += 30
          break;
          //items that contain a message and buttons
        case shStatus.drop.preapproval.waiting:
        case shStatus.drop.approval.eligibles.selected:
          height += 75
          break;
          //items that only contain buttons
        case shStatus.drop.preapproval.yes:
          height += 45
        default:
        }
      })
      return height
    },
    render() {
        return (
          <WindowScroller>
            {({height, isScrolling, scrollTop})=>(
              <List
                ref={(List)=>this.list = List}
              height={this.props.height}
              rowCount={this.data.shifts.length}
              rowHeight={this._getRowHeight}
              rowRenderer={this._renderDays}
              scrollTop={scrollTop}
              width={this.state.window.width}/>
            )}
          </WindowScroller>
        )
    },
});

Second component:

export const Actions = React.createClass({
    displayName: "Actions",
    mixins: [ReactMeteorData, OnResize],
    getMeteorData() {
        return {
            dropShifts: State.get(App.Constants.State.notifications.drop),
            waitingDrops: State.get(App.Constants.State.notifications.waitingDrops),
            swapShifts: State.get(App.Constants.State.notifications.swap),
            waitingSwaps: State.get(App.Constants.State.notifications.waitingSwaps),
            showDetail: State.get(App.Constants.State.notifications.showDetail),
            showSwap: State.get(App.Constants.State.notifications.showSwap),
            showOfferedSwaps: State.get(App.Constants.State.notifications.showOfferedSwaps),
            completed: State.get(App.Constants.State.notifications.completed),
            preapprove: State.get(App.Constants.State.pending.drop.approve),
            swapChoose: State.get(App.Constants.State.pending.swap.choose),
            offeredSwaps: State.get(App.Constants.State.pending.swap.offered),
            drop: State.get(App.Constants.State.pending.drop.drop),
            drop_job: State.get(App.Constants.State.pending.drop.job),
            drop_unit: State.get(App.Constants.State.pending.drop.unit),
            showCallManager: State.get(App.Constants.State.notifications.showCallManager)
        };
    },
    componentDidUpdate(){
      this.list.forceUpdateGrid()
      this.list.recomputeRowHeights()
    },
    //Shifts that show up in the needs attention area
    _needsAttention(){
      let attens = []
      this.data.dropShifts.map((shift) => {
        let temp = {}
        temp.shift = shift.shift
        let unit = Units.findOne({'zId' : shift.shift.zuus.unitId})
        temp.unit = {name: unit.name, timezone: unit.timezone}
        if (Jobs.findOne({'zId' : shift.shift.zuus.jobId}) !== undefined) {
          temp.job = {name: Jobs.findOne({'zId' : shift.shift.zuus.jobId}).name, color: 'doesnt matter right now'}
        }
        temp.state = shift.shift.state
        temp.notifName = shift.shiftOpener.staffFirstName + ' ' + shift.shiftOpener.staffLastName
        temp.dropName = ''
        temp.swapShift = ''
        //If the user is a manager
        if (Meteor.user().profile.zuus.isManager) {
          if (shift.shiftOpener.response.status == App.Constants.ShiftStatus.drop.approval.eligibles.accepted) {
            temp.buttons = ['drop manager post approval', 'drop manager post deny']
            shift.eligibles.forEach((eligible) => {
              if (eligible.currentResponse.status === App.Constants.ShiftStatus.drop.approval.eligibles.accepted) {
                temp.dropName = eligible.staffFirstName + ' ' + eligible.staffLastName
              }
            })
          } else if (shift.shiftOpener.response.status == App.Constants.ShiftStatus.drop.preapproval.yes){
            temp.buttons = ['manage fill']
          } else {
            temp.buttons = ['drop manager preapproval', 'drop manager pre deny']
          }
        }
        //If the user is a staff
        else { //direct assignment accept/deny
          if (shift.shiftOpener.response.status == App.Constants.ShiftStatus.drop.approval.assign) {
            temp.buttons = ['drop assignee accept', 'drop assignee deny']
          } else { //normal drop shift accept/ignore
            temp.buttons = ['eligible drop accept', 'eligible drop ignore']
          }
        }
        attens.push(temp)
        })
      this.data.swapShifts.map((shift) => {
        let shStatus = App.Constants.ShiftStatus
        let temp = {}
        temp.shift = shift.shiftSwapper.shift
        let unit = Units.findOne({'zId' : shift.shiftSwapper.shift.zuus.unitId})
        temp.unit = {name: unit.name, timezone: unit.timezone}
        if (Jobs.findOne({'zId' : shift.shiftSwapper.shift.zuus.jobId}) !== undefined) {
          temp.job = {name: Jobs.findOne({'zId' : shift.shiftSwapper.shift.zuus.jobId}).name, color: 'doesnt matter right now'}
        }
        temp.notifName = shift.shiftSwapper.staffFirstName + ' ' + shift.shiftSwapper.staffLastName
        temp.dropName = ''
        temp.swapShift = ''
        temp.state = shift.shiftSwapper.shift.state
        switch (shift.shiftSwapper.answer.status) {
        case shStatus.swap.choose_staff:
          //If you are the shiftswapper
          if (shift.shiftSwapper.staffId == Meteor.user().profile.zuus.personId) {
            temp.buttons = ['swapper see offers', 'cancel']
          } else { //if you are not the shiftswapper
            temp.buttons = ['swappee choose', 'swappee ignore']
          }
          break;
        case shStatus.swap.swappee_yes:
          break;
        case shStatus.swap.swapper.accepted: //Only the manager sees this state in the needs attention area
          temp.buttons = ['swap manager approve', 'swap manager deny']
          shift.candidates.forEach((candidate)=>{
            if(candidate.state.status == shStatus.swap.swapper.accepted){
              temp.swapShift = candidate
            }
          })
          break;
        default:
        }
        attens.push(temp)
        })
        return attens
    },
    //Shifts that show up in the pending section
    _pending(){
      let shStatus = App.Constants.ShiftStatus
      let pendings = []
      this.data.waitingDrops.map((shift) => {
        let temp = {}
        temp.shift = shift.shift
        let unit = Units.findOne({'zId' : shift.shift.zuus.unitId})
        temp.unit = {name: unit.name, timezone: unit.timezone}
        if (Jobs.findOne({'zId' : shift.shift.zuus.jobId}) !== undefined) {
          temp.job = {name: Jobs.findOne({'zId' : shift.shift.zuus.jobId}).name, color: 'doesnt matter right now'}
        }
        temp.swapShift = ''
        temp.buttons = ['cancel']
        if (Meteor.user().profile.zuus.isManager) {
          temp.buttons.push('manage fill')
        }
        temp.shiftDate = moment(shift.shift.start).format('dddd, MMMM Do')
        temp.notifName = shift.shiftOpener.staffFirstName + ' ' + shift.shiftOpener.staffLastName
        temp.dropName = ''
        temp.status = shift.shiftOpener.response.status
        pendings.push(temp)
      })
      this.data.waitingSwaps.map((shift) => {
        let temp = {}
        temp.shift = shift.shiftSwapper.shift
        let unit = Units.findOne({'zId' : shift.shiftSwapper.shift.zuus.unitId})
        temp.unit = {name: unit.name, timezone: unit.timezone}
        if (Jobs.findOne({'zId' : shift.shiftSwapper.shift.zuus.jobId}) !== undefined) {
          temp.job = {name: Jobs.findOne({'zId' : shift.shiftSwapper.shift.zuus.jobId}).name, color: 'doesnt matter right now'}
        }
        temp.dropName = ''
        temp.swapShift = ''
        temp.notifName = shift.shiftSwapper.staffFirstName + ' ' + shift.shiftSwapper.staffLastName
        temp.status = shift.shiftSwapper.answer.status
        if (shift.shiftSwapper.staffId == Meteor.user().profile.zuus.personId) {
          switch (shift.shiftSwapper.shift.state.status) {
          case shStatus.swap.choose_staff: //if you, as the swapper, have offers you need to choose from
            temp.buttons = ['swapper see offers']
            break;
          case shStatus.swap.swapper.accepted: // If you have chosen an offer and are waiting on manager approval
            temp.buttons = []
            shift.candidates.forEach((candidate)=>{
              if (candidate.state.status == shStatus.swap.swapper.accepted) {
                temp.swapShift = candidate
              }
            })
          default:
          }
        } else { //If you are not the swapper, you can see the status of shifts you have offered
          switch (shift.shiftSwapper.shift.state.status) {
          case shStatus.swap.choose_staff:
            temp.buttons = ['swappee see offers']
            break;
          case shStatus.swap.swapper.accepted:
            temp.buttons = []
            shift.candidates.forEach((candidate)=>{
              if (candidate.state.status == shStatus.swap.swapper.accepted) {
                temp.swapShift = candidate
              }
            })
            break;
          default:

          }
        }
        pendings.push(temp)
      })
      return pendings
    },
    _completed(){
      let shStatus = App.Constants.ShiftStatus
      let completed = []
      this.data.completed.map((shift) => {
        let temp = {}
        if (shift.droppedShift) {
          temp.shift = shift.droppedShift.shift
          let unit = Units.findOne({'zId' : shift.droppedShift.shift.zuus.unitId})
          temp.unit = {name: unit.name, timezone: unit.timezone}
          if (Jobs.findOne({'zId' : shift.droppedShift.shift.zuus.jobId}) !== undefined) {
            temp.job = {name: Jobs.findOne({'zId' : shift.droppedShift.shift.zuus.jobId}).name, color: 'doesnt matter right now'}
          }
          temp.swapShift = ''
          temp.dropName = shift.winner.firstName + ' ' + shift.winner.lastName
          temp.notifName = shift.droppedShift.shiftOpener.staffFirstName + ' ' + shift.droppedShift.shiftOpener.staffLastName
          if (temp.dropName == temp.notifName) {
            temp.dropName = ''
          }
          temp.status = shift.droppedShift.shift.state.status
        }
        if (shift.swappedShift) {
          temp.shift = shift.swappedShift.shiftSwapper.shift
          let unit = Units.findOne({'zId' : shift.swappedShift.shiftSwapper.shift.zuus.unitId})
          temp.unit = {name: unit.name, timezone: unit.timezone}
          if (Jobs.findOne({'zId' : shift.swappedShift.shiftSwapper.shift.zuus.jobId}) !== undefined) {
            temp.job = {name: Jobs.findOne({'zId' : shift.swappedShift.shiftSwapper.shift.zuus.jobId}).name, color: 'doesnt matter right now'}
          }
          temp.swapShift = ''
          temp.dropName = ''
          temp.notifName = shift.swappedShift.shiftSwapper.staffFirstName + ' ' + shift.swappedShift.shiftSwapper.staffLastName
          temp.status = shift.swappedShift.shiftSwapper.shift.state.status
          shift.swappedShift.candidates.forEach((candidate)=>{
            if(candidate.state.status == shStatus.swap.manager.yes){
              temp.swapShift = candidate
            }
          })
        }
        if (shift.deliveredShift) {
          temp.shift = shift.deliveredShift
          temp.unit = {name: Units.findOne({'zId' : shift.deliveredShift.zuus.unitId}).name, timezone: 'Australia/Sydney'}
          temp.job = {name: Jobs.findOne({'zId' : shift.deliveredShift.zuus.jobId}).name, color: 'doesnt matter right now'}
          temp.swapShift = ''
          temp.status = shift.deliveredShift.state.status
        }
        completed.push(temp)
      })
      return completed
    },
    _groupAll(){
      let allItems = []
      allItems.push({title: 'Needs Attention', items: this._needsAttention()})
      allItems.push({title: 'Waiting on Others', items: this._pending()})
      allItems.push({title: 'Completed', items: this._completed()})
      return allItems
    },
    _renderNotifications({key, index, style}){
      let all = this._groupAll()
      const category = all[index]
      return (
        <Paper
        key={key}
        style={style}>
          {this._renderItems(category)}
        </Paper>
      )
    },
    _renderItems(category){
      switch (category.title) {
      case "Needs Attention":
        return (
          <div>
            <Subheader
              style={{"backgroundColor": "#717171", 'color': '#f6f6f6', "fontFamily": "sans-serif", "fontWeight": "bold", 'boxShadow' : '2px 2px 3px rgba(0,0,0,.2)', marginBottom: '5px'}}>
              {category.title}
            </Subheader>
            <mList>
              {this._renderNeedsAttention(category.items)}
            </mList>
          </div>
        )
        break;
      case "Waiting on Others":
        return (
          <div>
            <Subheader
            style={{"backgroundColor": "#717171", 'color': '#f6f6f6', "fontFamily": "sans-serif", "fontWeight": "bold", 'boxShadow' : '2px 2px 3px rgba(0,0,0,.2)', marginBottom: '5px'}}>
            {category.title}
            </Subheader>
            <mList>
              {this._renderPending(category.items)}
            </mList>
          </div>
        )
        break;
      case "Completed":
        return (
          <div>
            <Subheader
              style={{"backgroundColor": "#717171", 'color': '#f6f6f6', "fontFamily": "sans-serif", "fontWeight": "bold", 'boxShadow' : '2px 2px 3px rgba(0,0,0,.2)'}}>
              {category.title}
            </Subheader>
            <mList>
              {this._renderCompleted(category.items)}
            </mList>
          </div>
        )
        break;
      default:

      }

    },
    _renderNeedsAttention(items){
      return items.map((shift) => {
        let date = moment(shift.shift).format('dddd, MMMM Do')
        if (shift.job) {
          return (
            <ShiftItem
              shift={shift.shift}
              unit={shift.unit}
              job={shift.job}
              buttons={shift.buttons}
              notifName={shift.notifName}
              dropName={shift.dropName}
              swapShift={shift.swapShift}
              status={shift.state.status}
              showDate='true'/>
          )
        } else {
          return (
            <ShiftItem
              shift={shift.shift}
              unit={shift.unit}
              buttons={shift.buttons}
              notifName={shift.notifName}
              dropName={shift.dropName}
              swapShift={shift.swapShift}
              status={shift.state.status}
              showDate='true'/>
          )
        }
      })
    },
    _renderPending(items){
      return items.map((shift) => {
        let date = moment(shift.shift.start).format('dddd, MMMM Do')
        if (shift.job) {
          return(
            <div>
            <ShiftItem
              shift={shift.shift}
              unit={shift.unit}
              job={shift.job}
              status={shift.status}
              date = {shift.shiftDate}
              swapShift={shift.swapShift}
              buttons = {shift.buttons}
              notifName = {shift.notifName}
              dropName = {shift.dropName}
              showDate='true'/>
            </div>
          )
        } else {
          return (
            <div>
            <ShiftItem
              shift={shift.shift}
              unit={shift.unit}
              status={shift.status}
              date = {shift.shiftDate}
              swapShift={shift.swapShift}
              buttons = {shift.buttons}
              notifName = {shift.notifName}
              dropName = {shift.dropName}
              showDate='true'/>
            </div>
          )
        }
      })
    },
    _renderCompleted(items){
      return items.map((shift) => {
        let date = moment(shift.shift.start).format('dddd, MMMM Do')
        if (shift.job) {
          return(
            <div>
            <ShiftItem
              shift={shift.shift}
              unit={shift.unit}
              job={shift.job}
              status={shift.status}
              notifName={shift.notifName}
              swapShift={shift.swapShift}
              dropName={shift.dropName}
              showDate='true'/>
            </div>
          )
        } else {
          return (
            <div>
            <ShiftItem
              shift={shift.shift}
              unit={shift.unit}
              status={shift.status}
              notifName={shift.notifName}
              swapShift={shift.swapShift}
              dropName={shift.dropName}
              showDate='true'/>
            </div>
          )
        }
      })
    },
    _getRowHeight({index}){
      let height = 60
      let all = this._groupAll()
      let category = all[index]
      category.items.forEach((i)=>{
        height += 195
        if (i.buttons && i.buttons.length) {
          height += 45
        }
        if (i.shift.notes !== undefined) {
          height += 44
        }
        if (i.swapShift !== "") {
          height += 170
        }
      })
      return height
    },
    render() {
      return(
        <WindowScroller>
          {({height, isScrolling, scrollTop})=>(
            <List
              ref={(List)=> this.list = List}
              height={this.state.window.height - 117}
              rowCount={3}
              rowHeight={this._getRowHeight}
              rowRenderer={this._renderNotifications}
              scrollTop={scrollTop}
              width={this.state.window.width}/>
          )}
        </WindowScroller>
      )
    }
});

My assumption is that I might be doing something wrong with the heights - the first component has the height passed into it via the parent component, while the second component gets the window height and subtracts the height of the header, as I would like the header to remain fixed, and not scroll with the rest of the page. Is this the correct way to achieve this? Is windowscroller the wrong component to be using if I don't want the entire window to scroll? Thanks again for all the help!

If you're experiencing the behavior only in Cordova, it's possible that it's related to Cordova? Have you searched for any similar reports regarding elastic scrolling / bouncing behavior in Cordova?

Is windowscroller the wrong component to be using if I don't want the entire window to scroll?

WindowScroller is the correct component to use if you want the body scrolling to control which windowed items are rendered.

I have researched a bit and had the thought that disabling the elastic behavior may solve the problem, but I'd prefer not to do that if I don't have to.
We didn't experience this behavior before implementing the WindowScroller so it seemed like maybe thats where it was stemming from.

Before using WindowScroller- the thing being scrolled was a div within the body. With WindowScroller, you're actually scrolling the whole document. So the browser (or whatever Cordova is wrapping around your bundle) may treat that differently in terms of the elastic snapping.

Unfortunately, setting "DisallowOverscroll" to true in the cordova preferences does not prevent the windowscroller from elastic snapping when I scroll past the top and bottom. Is there any way to disable this behavior within the component?

Elastic scroll snapping is not a property of the WindowScroller. It literally just listens to scroll events from the document level. That's it. 😄

Curious how things behave if you set -webkit-overflow-scrolling: auto

I will give that a shot an report back - I was actually able to prevent the header bar (and everything else) from moving, by styling my highest level div (that contains the entire app) with absolute positioning - this seemed to make the problem go away, minus a little flicker in the List content when I scroll past the top and it snaps back down.

Was this page helpful?
0 / 5 - 0 ratings