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.
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:

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.