React-Virtualized List skips rows when scrolling - javascript

I'm having an issue when I try to render a list of items as follows:
render()
{
const data = (my data);
const cellRenderer = ({index, key}) => (
<li key={key}>
<Datum data={data[index]} />
</li>
);
return (
<AutoSizer>
{
({ width, height }) => (
<CellMeasurer cellRenderer={ cellRenderer }
columnCount={ 1 }
width={ width }
rowCount={ transactions.length }>
{
({ getRowHeight }) => (
<List height={ height }
overscanRowCount={ 50 }
noRowsRenderer={ () => (<div> Nix! </div>) }
rowCount={ transactions.length }
rowHeight={ getRowHeight }
rowRenderer={ cellRenderer }
width={ width } />
)
}
</CellMeasurer>
)
}
</AutoSizer>
);
}
Now when I scroll down it skips every second list-item, until I end up with half the page empty but still scrollable.
When scrolling up it's even worse, it skips half the page.
AutoSizerand CellMeasurer seem to be working fine. I stepped through the code a bit, and it looks like they do create the correct measurement.
My data is just a static array of objects. Not a promise or stream.
I also changed my Datum component a few times to make sure it's completely static markup but that didn't change anything.
Any ideas anybody?
[edit]
Here's a Plunker showing my problem: https://plnkr.co/edit/2YJnAt?p=preview
Ironically, while fiddling with it, I accidentally figured out what I had done wrong. I'll submit an answer with my solution.

Right, I found the problem (and so did #MBrevda! +1!)
The rowRenderer method takes a style that needs to be applied to the rendered list element: https://plnkr.co/edit/FzPwLv?p=preview

Related

`react-transition-group` evaluating both if AND else expressions on a ternary expression

I have a component that renders a list, and this list of items can be increased. I'm wrapping the list around a <TransitionGroup> to animate new items being added.
There's a message when we don't have items on the list. However, even when items are added, the message won't go away. I tested it without <TransitionGroup> and it works normally. I have a minimal example of the problem on CodeSandbox.
The code for the component is:
const ItemsList = ({ items }) => {
return (
<TransitionGroup component="ul" timeout={400}>
{items.length === 0
? "There is no items"
: items.map((item, i) => (
<CSSTransition key={i} classNames="item-animation">
<li key={i}>{item}</li>
</CSSTransition>
))}
</TransitionGroup>
);
};
After adding an element to the list, the message should disappear, but it's still visible.
My guess is there's something different react-transition-group uses on render, not rendering the full component as we write it. But how to fix this problem?
Try rendering Transition Group component only if items.length !=0
So your code should look something like this:
items.length !=0 ? `render your component` : "There is no item"

React Native Tab view always has the height equal to height of the highest tab

Introduction
I have a FlatList that renders a Tab View in its footer. This Tab View let the user switch between one FlatList or an other. So, these last are sibling FlatLists.
Problem
The first FlatList "A" has a greater height than the second one "B". When I choose the second list, its height is the same as the "A" FlatList's one.
I have reproduced the problem in a snack where you can see a similar code. I recognize that the code is a little long but too simple, just focus only on the parent FlatList (in the App component) and the two FlatLists that are rendered in each tab (at the end of the code)
QUESTION
Any ideas how to solve this issue? I don't know if the problem is in the styles or if I have to do something else to make this work (all flatlists have to have their own height, not the greater).
Thank you, I would really appreciate your help.
UPDATE 2022
const renderScene = ({ route }) => {
//
// 📝 Note: We are hidding tabs in order to avoid the
// "FlexBox Equal Height Columns" typical problem
//
switch (route.key) {
case "bitcoin":
return (
<View style={index !== 0 && styles.hidden}>
<Bitcoin />
</View>
);
case "ethereum":
return (
<View style={index !== 1 && styles.hidden}>
<Etherum />
</View>
);
case "rose":
return (
<View style={index !== 2 && styles.hidden}>
<Rose />
</View>
);
default:
return null;
}
};
...
<TabView
renderTabBar={renderTabBar}
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={handleOnIndexChange}
initialLayout={{ width: layout.width }}
removeClippedSubviews={false}
swipeEnabled
swipeVelocityImpact={0.2}
gestureHandlerProps={{
activeOffsetX: [-30, 30], // To solve swipe problems on Android
}}
style={globalStyles.flexContainer}
/>
Styles:
hidden: { display: "none" }
I have updated the snack with the solution!
As in the snack I implemented my own TabView, I have decided to implement the same solution with the library "react-native-tab-view", as it is the best tab for react native for now.
Think that some people having this issue will be able to solve it.
Basically, what we need to do is to dinamically calculate the height of each tab scene and pass it to the style of the TabView using the onLayout prop.
Just like this:
const renderScene = ({ route }) => {
switch (route.key) {
case "inifiniteScrollFlatList":
return (
<FirstRoute />
);
case "rawDataFlatList":
return (
<View
onLayout={(event) => setTab1Height(event.nativeEvent.layout.height + TAB_HEIGHT)}
>
<SecondRoute />
</View>
);
case "otherRawDataFlatList":
return (
<View
onLayout={(event) => setTab2Height(event.nativeEvent.layout.height + TAB_HEIGHT)}
>
<ThirdRoute />
</View>
);
default:
return null;
}
};
<TabView
style={ index !== 0 && {
height: index === 1 ? tab1Height : tab2Height,
}}
renderTabBar={renderTabBar}
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
removeClippedSubviews={false} // Pd: Don't enable this on iOS where this is buggy and views don't re-appear.
swipeEnabled={true}
/>
Pd: You shouldn't do this with a tab that uses an infinite scroll with pagination. Instead, you will have to set the height to null to allow the parent FlatList to automatically get its height.
So, I haven't gone through the entire code, nor did I find a solution to your height problem.
But, what you can do is check out React navigation 5 -> createMaterialTopTabNavigator.
It lets you create tabs with separate flatlist for each tab. It will solve the height problem because what is being rendered are separate flatlists as per the active tab. And it will also, make your code much cleaner.
You won't have to use a Flatlist with header and footer components to render the tabs with nested Flatlists.
And if you want to hide the tabs on scroll, that is possible by passing props to the tab navigator that toggle visibility on scroll of the Flatlist using the onScroll event that is called when a Flatlist is scrolled. The same can be done for the visibility of the header as well. And with the proper animations it would look as if the header and the tabs were pushed up on scroll like it does right now.
I ended up removing the tabs content from the tab control altogether. It's hacky but worked for me...
render() {
const {index} = this.state;
return (
<ScrollView>
<TabView
renderPager={this._renderPager}
renderScene={() => null}
onIndexChange={index => this.setState({index})}
initialLayout={{height: 0, width: Dimensions.get('window').width}}
/>
{index === 0 && <Tab1Content />}
{index === 1 && <Tab2Content />}
</ScrollView>
);
Source:- https://github.com/satya164/react-native-tab-view/issues/290#issuecomment-447941998

Cells overlapping problems in react virtualized with cell measurer

I am using React Virtualized to window my items list which has the following functionality
1) onClicking on any item, the user will be prompted with a details panel where he will be able to update the details of the item. And when he goes back to the list, he will be able to see the details in the item cell in the list. He can add a lot of details to make it bigger or reduce the size by removing the details
2) He can delete the item, or add another item to the list with certain details or without details.
CellMeasurer serves my requirement as dynamic height is supported. But I am having following issues with it
1) initially when my list mounts for the first time, first few items are measured and shown correctly but as soon as I scroll to the end, items get overlapped with each other.(positining isnt correct, I am guessing the defaultHeight is being applied to the unmeasured cells). This works fine as soon as the list is rerendered again.
2) Also, when I am updating the details of an item the list doesnt update with the new height adjustments
I am sure that somewhere my implementation is incorrect but have spent a lot of time hammering my head for it. Please let me know what can be done here to fix this
ItemView = props => {
const {index, isScrolling, key, style} = props,
items = this.getFormattedItems()[index]
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
isScrolling={isScrolling}
key={key}
rowIndex={index}
parent={parent}>
{({measure, registerChild}) => (
<div key={key} style={style} onLoad={measure}>
<div
onLoad={measure}
ref={registerChild}
{...parentProps}>
<Item
onLoad={measure}
key={annotation.getId()}
{...childProps}
/>
</div>
</div>
)}
</CellMeasurer>
)
}
renderDynamicListOfItems(){
return (<div>
<List
height={500}
ref={listComponent => (_this.listComponent = listComponent)}
style={{
width: '100%'
}}
onRowsRendered={()=>{}}
width={1000}
rowCount={props.items.length}
rowHeight={this._cache.rowHeight}
rowRenderer={memoize(this.ItemView)}
// onScroll={onChildScroll}
className={listClassName}
overscanRowCount={0}
/>
</div>
)
}
Also, I am manually triggering the remeasurement of my item in its componentDidUpdate like follows()
Component Item
...
componentDidUpdate() {
console.log('loading called for ', this.props.annotation.getId())
this.props.onLoad()
}
...
In the main parent I am recomputing the heights of the list every time the list has updated and triggering a forceupdate as follows
Component ParentList
...
componentDidUpdate() {
console.log("calling this parent recomputing")
this.listComponent.recomputeRowHeights()
this.listComponent.forceUpdateGrid()
}
...
I just faced the same issue and it turned out to be some layout updates in my Item component, that changed the height of the item after it has been measured by the CellMeasurer.
Fix was to pass down the measure function and call it on every layout update.
In my case it was
images being loaded
and text being manipulated with Twemoji (which replaces the emojis with images).
I also had problems getting the correct height from the cache after prepending some new list items when scrolling to the top of the list. This could be fixed by providing a keyMapper to the CellMeasurerCache:
const [cache, setCache] = useState<CellMeasurerCache>();
useEffect(() => {
setCache(new CellMeasurerCache({
keyMapper: (rowIndex: number, columnIndex: number) => listItems[rowIndex].id,
defaultHeight: 100,
fixedWidth: true,
}));
}, [listItems]);

I have a <List /> with row items of unknown DOM height

Given a react-virtualized List with variable content in each row the DOM height needs to be calculated by a rowHeight function - however since that gets called before the row is rendered I am unsure how to actually get the row height.
The examples given for dynamic List row height basically go off a predefined number in the list item's props which doesn't help.
What I think I want to do is render the row on the page at a default height and then get the overflow height on the DOM and set that as the row height. How can I hook into an afterRowRender function or something like that? I imagine performance would suffer so maybe there is a better way of doing this that I am missing.
Check out the docs on CellMeasurer. You can see it in action here.
Basically you'll be looking for something like this:
import React from 'react';
import { CellMeasurer, CellMeasurerCache, Grid } from 'react-virtualized';
const cache = new CellMeasurerCache({
fixedWidth: true,
minHeight: 50,
});
function rowRenderer ({ index, isScrolling, key, parent, style }) {
const source // This comes from your list data
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{({ measure }) => (
// 'style' attribute required to position cell (within parent List)
<div style={style}>
// Your content here
</div>
)}
</CellMeasurer>
);
}
function renderList (props) {
return (
<List
{...props}
deferredMeasurementCache={cache}
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
/>
);
}

Dynamic height with infinite scroll (react-virtualized)

I'm using 'react-virtualized' to render an infinite scrolling list.
However, I am having trouble rendering the rowHeight dynamically. I made an attempt, but it only is relevent for desktop, and feels wrong.
I tried following the examples, but had no success.
What is the correct way to calculate the true row height?
It should be responsive to mobile.
Here is an example:
https://codesandbox.io/s/ADARgvlxB
class App extends React.Component {
render() {
return (
<div>
<AutoSizer>
{({ height, width }) => (
<List
height={600}
width={width}
rowCount={foo.length}
rowHeight={({ index }) => {
const x = foo[index];
if (x.name.length < 10) { return 20; }
else if (x.name.length > 9) { return 40;}
}}
rowRenderer={({ index, style }) => {
const x = foo[index];
return (
<div key={index} style={style}>
{x.name}
</div>
);
}}
/>
)}
</AutoSizer>
</div>
);
}
}
What is the correct way to calculate the true row height?
This is what the react-virtualized CellMeasurer component is for. You can see a demo of it measuring dynamic heights here. The source code for the demo are the *.example.js files here.

Categories

Resources