Prevent inverted Flatlist from scrolling to bottom when new items are added - react-native

I am building a chat app, using an inverted Flatlist. I add new items to the top of the list when onEndReached is called and everything works fine.
The problem is that if add items to the bottom, it instantly scrolls to the bottom of the list. That means that the user has to scroll back up to read the messages that were just added (which is terrible).
I tried to call scrollToOffset in onContentSizeChange, but this has a one-second delay where the scroll jumps back and forth.
How can I have the list behave the same way when I add items to the top AND to the bottom, by keeping the same messages on screen instead of showing the new ones?

here is demo: https://snack.expo.io/#nomi9995/flatlisttest
Solution 1:
use maintainVisibleContentPosition props for preventing auto scroll in IOS but unfortunately, it's not working on android. but here is PR for android Pull Request. before merge this PR you can patch by own from this PR
<FlatList
ref={(ref) => { this.chatFlatList = ref; }}
style={styles.flatList}
data={this.state.items}
renderItem={this._renderItem}
maintainVisibleContentPosition={{
minIndexForVisible: 0,
}}
/>
Solution 2:
I found another workaround by keep latest y offset with onScroll and also save content height before and after adding new items with onContentSizeChange and calculate the difference of content height, and set new y offset to the latest y offset + content height difference!

Here I am adding a new item on top and bottom in an inverted Flatlist.
I hope you can compare your requirements with the provided sample code :)
Full Code:
import React, {Component} from 'react';
import {
SafeAreaView,
View,
FlatList,
StyleSheet,
Text,
Button,
Platform,
UIManager,
LayoutAnimation,
} from 'react-native';
if (Platform.OS === 'android') {
if (UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
const getRandomColor = () => {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
const DATA = [
getRandomColor(),
getRandomColor(),
getRandomColor(),
getRandomColor(),
getRandomColor(),
];
export default class App extends Component {
scrollValue = 0;
append = true;
state = {
data: DATA,
};
addItem = (top) => {
const {data} = this.state;
let newData;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
if (top) {
newData = [...data, getRandomColor()];
this.setState({data: newData});
} else {
newData = [getRandomColor(), ...data];
this.setState({data: newData});
}
};
shouldComponentUpdate() {
return this.scrollValue === 0 || this.append;
}
onScrollBeginDrag = () => {
this.append = true;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({});
};
render() {
const {data} = this.state;
return (
<SafeAreaView style={styles.container}>
<Button title="ADD ON TOP" onPress={() => this.addItem(true)} />
<FlatList
data={data}
onScrollBeginDrag={this.onScrollBeginDrag}
renderItem={({item}) => <Item item={item} />}
keyExtractor={(item) => item}
inverted
onScroll={(e) => {
this.append = false;
this.scrollValue = e.nativeEvent.contentOffset.y;
}}
/>
<Button title="ADD ON BOTTOM" onPress={() => this.addItem(false)} />
</SafeAreaView>
);
}
}
function Item({item}) {
return (
<View style={[styles.item, {backgroundColor: item}]}>
<Text style={styles.title}>{item}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
height: 100,
},
title: {
fontSize: 32,
},
});

This is one year late, but this works fine:
<FlatList
inverted
initialScrollIndex={1}
{...}
/>
Since inverted renders flatlist but with inverted: 1, thus you need to pass 1 to initialScrollIndex so that it scrolls to bottom in normal list and to top in the inverted one

Have you tried using keyExtractor?
It may help react avoid re-render, so try use unique keys for each item.
you can read more about it here: https://reactnative.dev/docs/flatlist#keyextractor

Related

mobile app want graphical text to change font size per line automatically, like on tiktok/instagram alternating lines different sizes

On TikTok and Instagram, they can generate automatically text font size like this image, where alternating lines have different font sizes automatically. I'm trying to figure out how to code that in React Native for mobile IOS and Android: [[enter image description here](https://i.stack.imgur.com/vkhIo.jpg)](https://i.stack.imgur.com/XcjLq.jpg)
I couldn't figure it out. I made something that I'm not crazy about, which is just having a larger font on the first three lines and then a smaller font. See image: But I don't like it. enter image description here
I totally misunderstood what PixelRatio.getFontScale did. I thought it would provide the average width a single character takes up on screen. If you can find a way to get a rough estimate of the width of a single character, then this method will work link:
import { useEffect, useState } from 'react';
import { View, StyleSheet, Text, PixelRatio } from 'react-native';
import rnTextSize from 'react-native-text-size';
import reduce from 'awaity/reduce';
import useViewLayout from './useViewLayout';
const fontScale = PixelRatio.getFontScale();
const showLayoutValues = true
export default function MultiLineText({
width,
containerStyle,
textStyle1 = { fontSize: 16 },
textStyle2 = { fontSize: 22 },
str,
...textProps
}) {
// containerLayout will provide the max width each line can have
const [containerLayout, onContainerLayout] = useViewLayout();
// lines was created in a useMemo hook but I wasnt sure if
// useMemo could handle async
const [lines, setLines] = useState([]);
useEffect(() => {
const calcLines = async () => {
let words = str.split(' ').filter((s) => s.trim().length);
let newLines = await words.reduce(
async (prevPromise, curr, index) => {
const prev = await prevPromise;
let lineIndex = prev.length - 1;
let style = index % 2 == 0 ? textStyle1 : textStyle2;
const fontSize = style.fontSize;
// I wanted to use this https://github.com/aMarCruz/react-native-text-size/
// to measure text width but expo doesnt support it
const config = {
// if you exported from expo and link rnTextSize set this to true
useMeasureModule:false,
fontProps:style
}
const useMeasureModule = false;
let lineWidth = await getTextWidth(
prev[lineIndex],
fontSize,
config
);
let wordWidth = await getTextWidth(curr, fontSize, config);
// if currentLine can fit the next word add it
if (lineWidth + wordWidth < (width || containerLayout.width))
prev[lineIndex] += curr + ' ';
// or put it on the next line
else {
prev[lineIndex + 1] = curr + ' ';
}
return prev;
},
['']
);
setLines(newLines);
};
calcLines();
}, [str, containerLayout, width, textStyle1, textStyle2]);
return (
<>
{showLayoutValues && <Text>Container Layout: {JSON.stringify(containerLayout,null,4)}</Text>}
<View
style={[styles.container, containerStyle]}
onLayout={onContainerLayout}>
{lines.map((line, i) => (
<Text
{...textProps}
// to ensure that lines dont wrap
numberOfLines={1}
style={[textProps.style, i % 2 == 0 ? textStyle1 : textStyle2]}>
{line}
</Text>
))}
</View>
</>
);
}
const getTextWidth = async (str, fontSize, config={}) => {
const {fontProps, useMeasureModule} = config;
if (!useMeasureModule) {
// random mathing
return str.length * fontScale * fontSize ** 0.8;
}
let measure = await rnTextSize.measure({
...fontProps,
text: str,
});
return measure.width;
};
const styles = StyleSheet.create({
container: {
width: '100%',
},
});
And then in use:
export default function App() {
return (
<View style={styles.container}>
<MultiLineText
containerStyle={{
width: '100%',
backgroundColor: '#eef',
alignSelf: 'center',
alignItems: 'center',
}}
textStyle1={styles.text}
textStyle2={styles.text2}
str="I am really not sure as of how long this text needs to be to exceed at least 3 lines. I could copy and paste some stuff here but I think if I just type and type it would be quicker than googling, copying, and pasting"
/>
</View>
);
}
I just found out that onTextLayout is a thing. It gives about each line in the Text component, including info about the characters present on each line. This could be used to figure out where to break lines of text (no planned web support):
After tying the str prop to a text input it became very clear that it is ideal to prevent this component from re-rendering as much as possible so I made additional changes (demo)
import { useState, useCallback, useEffect, memo } from 'react';
import { View, StyleSheet, Text, ScrollView } from 'react-native';
// text lines will alternate between these styles
const defaultLineStyles = [
{ color: 'red', fontSize: 16 },
{ color: 'blue', fontSize: 22 },
{ color: 'green', fontSize: 28 },
];
function MultiLineText({
containerStyle,
lineStyles = defaultLineStyles,
str,
...textProps
}) {
const [lines, setLines] = useState([]);
// each time a substring is added to line,
// remove the substring from remainingStr
const [remainingStr, setRemainingStr] = useState('');
const onTextLayout = useCallback((e) => {
// the first line of text will have the proper styling
let newLine = e.nativeEvent.lines[0].text;
setLines((prev) => {
return [...prev, newLine];
});
// remove newLine from remainingStr
setRemainingStr((prev) => prev.replace(newLine, ''));
}, []);
// when str changes reset lines, and set remainingStr to str
useEffect(() => {
setLines([]);
setRemainingStr(str);
}, [str]);
return (
<>
<View style={[styles.container, containerStyle]}>
<ScrollView style={{ flex: 1 }}>
{lines.map((line, i) => (
<Text
{...textProps}
style={[textProps.style, lineStyles[i % lineStyles.length]]}>
{line}
</Text>
))}
</ScrollView>
{/* this view will be invisible*/}
{remainingStr.length > 0 && (
<View style={{ opacity: 0 }}>
<Text
{...textProps}
onTextLayout={onTextLayout}
style={[
textProps.style,
// use lines.length to get proper style
lineStyles[lines.length % lineStyles.length],
]}>
{remainingStr}
</Text>
</View>
)}
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
height:'50%'
},
});
// be careful when passing non memoized array/objects
export default memo(MultiLineText)
Its important to note that objects/arrays that arent memoized/state/refs will cause the memoized component to re-render, even if the values are static e.g
<MultiLineText
containerStyle={{
width: '100%',
height: 200,
backgroundColor: '#eef',
alignSelf: 'center',
alignItems: 'center',
}}
style={styles.defaultTextStyle}
str={text}
lineStyles={[styles.text,styles.text2]}
/>
containerStyle and lineStyles are getting new objects and arrays every time its parent component re-render, which will make MultiLineText re-render (even though its memoized). After moving the containerStyle to the stylesheet and memoizing lineStyles re-rendering becomes better:
const lineStyles = React.useMemo(()=>{
return [styles.text,styles.text2]
},[])
return (
<View style={styles.container}>
<TextInput onChangeText={setText} label="Enter some text" value={text} />
<MultiLineText
containerStyle={styles.textContainer}
style={styles.defaultTextStyle}
str={text}
lineStyles={lineStyles}
/>
</View>

How do I build a multi-card carousel?

I'm trying to build something like this in React Native. It will stretch across the whole page and will loop infinitely, there will be a 'next' and 'previous' button.
I'm new to React Native (coming from React), so am a little unsure about how to implement it.
I found this guide on YouTube helpful to get something very basic up and running.
Here is the code I have so far:
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {withTheme} from 'react-native-paper';
import {
View,
StyleSheet,
Text,
Dimensions,
Image,
FlatList,
Pressable,
} from 'react-native';
import PrismicText from '../prismicText';
const {width: windowWidth, height: windowHeight} = Dimensions.get('window');
const Slide = ({data}) => {
return (
<View
style={{
height: 400,
width: 300,
justifyContent: 'center',
alignItems: 'center',
marginRight: 15,
}}>
<Image
source={{uri: data.image}}
style={{width: '100%', height: '100%', borderRadius: 16}}></Image>
</View>
);
};
const Carousel = ({slice, theme}) => {
const slideList = slice.items.map((item, index) => {
return {
id: index,
image: item.image.url,
};
});
const {colors, isTabletOrMobileDevice} = theme;
const styles = isTabletOrMobileDevice ? mobileStyles : desktopStyles;
const flatListRef = useRef(null);
const viewConfig = {viewAreaCoveragePercentThreshold: 50};
const [activeIndex, setActiveIndex] = useState(4);
const onViewRef = useRef(({changed}) => {
if (changed[0].isViewable) {
setActiveIndex(changed[0].index);
}
});
const handlePressLeft = () => {
if (activeIndex === 0)
return flatListRef.current?.scrollToIndex({
animated: true,
index: slideList.length - 1,
});
flatListRef.current?.scrollToIndex({
index: activeIndex - 1,
});
};
const handlePressRight = () => {
if (activeIndex === slideList.length - 1)
return flatListRef.current?.scrollToIndex({
animated: true,
index: 0,
});
flatListRef.current?.scrollToIndex({
index: activeIndex + 1,
});
};
return (
<>
<View
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 8,
}}>
<Pressable style={[styles.chevron]} onPress={handlePressLeft}>
Left
</Pressable>
<Pressable style={[styles.chevron]} onPress={handlePressRight}>
Right
</Pressable>
</View>
<FlatList
ref={ref => (flatListRef.current = ref)}
data={slideList}
horizontal
showsHorizontalScrollIndicator={false}
snapToAlignment="center"
pagingEnabled
viewabilityConfig={viewConfig}
onViewableItemsChanged={onViewRef.current}
renderItem={({item}, i) => <Slide data={item} />}
keyExtractor={item => item}
/>
<View style={styles.index}>
<Text category={'c2'} style={styles.indexText}>
{activeIndex + 1} of {slideList.length} photos
</Text>
</View>
</>
);
};
const mobileStyles = StyleSheet.create({});
const desktopStyles = StyleSheet.create({});
export default withTheme(Carousel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The problems I'm experiencing with this code:
I am setting the initial state of the active index to 4, but active index always starts at 0
Clicking the 'right' button doesn't seem to change the active index
Clicking the 'right' button will move the carousel along by 1 increment, but it won't go any further (even on smaller viewports like mobile where you can only see 1.5 cards, so it should move along many times to be able to see all of the cards)
Clicking the 'left' button seems to have the same issues as above
There is no infinite loop of the slides
My feeling is that there are two issues to be addressed:
Active index is broken and needs to be fixed
Modifications required to make the number of cards on the viewport responsive
I've spent a lot of time looking at this and can't seem to figure it out. Any help would be much appreciated.
The first issue is easy to fix. You are expecting that the FlatList scrolls initially to the initial activeIndex, but you are not telling the FlatList to do so. There is a prop called initialScrollIndex that is designed for this purpose.
<FlatList
initialScrollIndex={4}
...
The second issue is caused by a faulty implementation of the functions handlePressLeft and handlePressRight as well as providing
const onViewRef = useRef(({changed}) => {
if (changed[0].isViewable) {
setActiveIndex(changed[0].index);
}
});
I have removed the above completely.
I have changed the activeIndex state to the following.
const [activeIndex, setActiveIndex] = useState({index: 4, direction: 'right'});
I have changed the handlePressLeft and handlePressRight functions to the following.
const handlePressLeft = () => {
setActiveIndex((prev) => ({index: prev.index - 1, direction: 'left'}));
};
const handlePressRight = () => {
setActiveIndex((prev) => ({index: prev.index + 1, direction: 'right'}));
};
I have created an effect as follows.
React.useEffect(() => {
if (activeIndex.index === slideList.length - 1 && activeIndex.direction === 'right') {
setActiveIndex({index: 0, direction: 'right'});
} else if (activeIndex.index < 0 && activeIndex.direction === 'left') {
setActiveIndex({index: slideList.length - 2, direction: 'left'})
} else {
flatListRef.current?.scrollToIndex({
animated: true,
index: activeIndex.index,
});
}
}, [activeIndex, slideList.length]);
I have implemented an adapted snack without images and using a dummy array.
You can use the below third-party library to achieve the above one quickly.
react-native-snap-carousel
You can check all the examples and use them according to your requirement.
Hope it will help you!
You should try react-native-reanimated-carousel.
Why?
highly customizable + easy and fast to implement any carousel
It's new and it uses react-native-reanimated for better performance (by running animations on the UI thread, rather than on JS thread)
solves all the issues that react-native-snap-carousel has (which is deprecated and has lots of bugs)
solves all the issues that you have and handles many edge cases that you may have forgotten about (in case you want to implement it by yourself)

Increase height of a View on swipe up in react native expo

I have a container that contains multiple views like this :
export default function MyComponent() {
<View *** container *** >
<View> // some stuff </View>
<View> // some stuff </View>
<ScrollView> // some stuff </ScrollView>
</View
}
The ScrollView is about 40% of the container's height, in absolute position.
What I need to do is to be able to extend it over the whole screen with a swipe up.
I tried to use some modals npm package but I can't make it work.
A few things:
From my experience, ScrollViews and FlatLists work best when they have a flex of one and are wrapped in a parent container that limits their size.
I couldnt determine if you wanted to wrap the entire screen in a GestureDector and listen to swipes or if you only wanted the ScrollView to listen for scroll events. Because you want the ScrollView to take up the entire screen, I assume you wanted to listen to onScroll events
So here's a demo I put together:
import * as React from 'react';
import {
Text,
View,
Animated,
StyleSheet,
ScrollView,
useWindowDimensions
} from 'react-native';
import Constants from 'expo-constants';
import Box from './components/Box';
import randomColors from './components/colors'
const throttleTime = 200;
// min time between scroll events (in milliseconds)
const scrollEventThrottle = 100;
// min up/down scroll distance to trigger animatino
const scrollYThrottle = 2;
export default function App() {
const scrollViewAnim = React.useRef(new Animated.Value(0)).current;
let lastY = React.useRef(0).current;
// used to throttle scroll events
let lastScrollEvent = React.useRef(Date.now()).current;
const [{ width, height }, setViewDimensions] = React.useState({});
const [isScrollingDown, setIsScrollingDown] = React.useState(false);
const [scrollViewTop, setScrollViewTop] = React.useState(400);
// scroll view is 40% of view height
const defaultHeight = height * .4;
// call onLayout on View before scrollView
const onLastViewLayout = ({nativeEvent})=>{
// combine the y position with the layout height to
// determine where to place scroll view
setScrollViewTop(nativeEvent.layout.y + nativeEvent.layout.height)
}
const onContainerLayout = ({nativeEvent})=>{
// get width and height of parent container
// using this instead of useWindowDimensions allow
// makes the scrollView scale with parentContainer size
setViewDimensions({
width:nativeEvent.layout.width,
height:nativeEvent.layout.height
})
}
//animation style
let animatedStyle = [styles.scrollView,{
height:scrollViewAnim.interpolate({
inputRange:[0,1],
outputRange:[defaultHeight,height]
}),
width:width,
top:scrollViewAnim.interpolate({
inputRange:[0,1],
outputRange:[scrollViewTop,-10]
}),
bottom:60,
left:0,
right:0
}]
const expandScrollView = ()=>{
Animated.timing(scrollViewAnim,{
toValue:1,
duration:200,
useNativeDriver:false
}).start()
}
const shrinkScrollView = ()=>{
Animated.timing(scrollViewAnim,{
toValue:0,
duration:200,
useNativeDriver:false
}).start()
}
const onScroll=(e)=>{
// throttling by time between scroll activations
if(Date.now() - lastScrollEvent <scrollEventThrottle ){
console.log('throttling!')
return
}
lastScrollEvent = Date.now()
// destructure event object
const {nativeEvent:{contentOffset:{x,y}}} = e;
const isAtTop = y <= 0
const isPullingTop = lastY <= 0 && y <= 0
let yDiff = y - lastY
let hasMajorDiff = Math.abs(yDiff) > scrollYThrottle
// throttle if isnt pulling top and scroll dist is small
if(!hasMajorDiff && !isPullingTop ){
return
}
const hasScrolledDown = yDiff > 0
const hasScrolledUp = yDiff < 0
if(hasScrolledDown){
setIsScrollingDown(true);
expandScrollView()
}
if(isAtTop || isPullingTop){
setIsScrollingDown(false)
shrinkScrollView();
}
lastY = y
}
return (
<View style={styles.container} onLayout={onContainerLayout}>
<Box color={randomColors[0]} text="Some text"/>
<Box color={ randomColors[1]} text="Some other text "/>
<View style={styles.lastView}
onLayout={onLastViewLayout}>
<Text>ScrollView Below </Text>
</View>
<Animated.View style={animatedStyle}>
<ScrollView
onScroll={onScroll}
style={{flex:1}}
>
{randomColors.map((color,i)=>
<Box color={color} height={60} text={"Item Number "+(i+1)}/>
)}
</ScrollView>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
// justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
},
scrollView:{
// position:'absolute',
position:'absolute',
marginVertical:10,
height:'40%',
backgroundColor:'lightgray'
},
lastView:{
alignItems:'center',
paddingVertical:5,
borderBottomWidth:1,
borderTopWidth:1
}
});
The result is that on downward scrolling, the scrollview expands and takes up the entire screen, and shrinks when the user scrolls to the top.
Edit : I found that simply grabbing the y position and the height of the view directly before the scroll view made it easy to calculate where the position the ScrollView, allowing for the ScrollView to be positioned absolute all the time.
Here is a very basic example of how to use FlatList (similar to ScrollView) and allow for the scrolling behavior you are wanting:
import React from "react";
import {Text,View} from "react-native";
const App = () => {
const myData = {//everything you want rendered in flatlist}
const renderSomeStuff = () => {
return (
<View>
<Text> Some Stuff </Text>
</View>
)
};
const renderOtherStuff = () => {
return (
<View>
<Text> Other Stuff </Text>
</View>
);
};
return (
<View>
<FlatList
data={myData}
keyExtractor={(item) => `${item.id}`}
showsVerticalScrollIndicator
ListHeaderComponent={
<View>
{renderSomeStuff()}
{renderOtherStuff()}
</View>
}
renderItem={({ item }) => (
<View>
<Text>{item}</Text>
</View>
)}
ListFooterComponent={
<View></View>
}
/>
</View>
);
};
export default App;

How can I display 30 pages of text in a (scrolling) screen

I want to display 30 pages of text on a screen. I've tried ScrollView and FlatList but I get a white screen. Only when I try with ScrollView to display only 2 pages, works fine.
I do not want to use a WebView, because I would like to have all data in the app (no internet connection needed).
Here is what I've already tried:
With FlatList:
I have a text.js as a model, which I use to create a Text Object in an array, which I then use as data for the FlatList. For the renderItem function (of FlatList) I use a TextItem to display the text.
text.js
function Text(info) {
this.id = info.id;
this.text = info.text;
}
export default Text;
LongTextModule.js
import Text from '../../models/text';
export const LONGTEXT = [
new Text({
id:'text_1',
text:`.....longtext....`
})
]
TextItem.js
const TextItem = (props) => {
return (
<View style={styles.screen} >
<Text style={styles.textStyle}>{props.longText}</Text>
</View >
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
},
textStyle: {
justifyContent: 'flex-start',
alignItems: 'flex-start',
fontFamily: 'GFSNeohellenic-Regular',
fontSize: 20,
padding: 10,
}
});
TextDetailScreen.js
const TextDetailScreen = (props) => {
const renderText = data => {
return <TextItem longText={data.item.text} />
}
return <FlatList
data={LONGTEXT}
keyExtractor={(item, index) => item.id}
renderItem={renderText}
/>
};
I think it's needless to show the code with ScrollView, since ScrollView is only for a small list.
I even tried to render the longText like this in the screen.
Without the ScrollView I get the first portion, but with ScrollView a white screen.
const TextDetailScreen = (props) => {
return (
<ScrollView>
<Text> ...longText...</Text>
</ScrollView>
);
};
I'm sure there is a way to display a lot of pages of text on a screen?
But how?
Thank you :)
It seems not to be an unknown Issue, I've also read from time to time about this issue.
But not to use Webview, because you wan't to have all Data in your app - don't have to be an Argument against Webview. With WebView, you also can display Data from your App-Storage.
Example:
<WebView style={styles.myStyle} source={{html: `<p style="font-size:48px">${longtext}</p>`}} />

FlatList rendered row but not displaying items

I am trying to make a FlatList with items that can expand and collapse onPress
However, when I add a new item from another screen then go back to SearchListScreen, it will only display 2 items, but the FlatList does render the correct number of rows.
example:
Before adding new item
After adding new item
The same thing happens when I remove an item or expand a item.
Here's my code:
SearchList.js
import React, { Component } from 'react'
import { Text, View, FlatList, StyleSheet } from 'react-native'
import SearchCard from './SearchCard'
export default class SearchList extends Component {
wrapperStyle (index) {
return index > 0 ? styles.listItemWrapper : [styles.listItemWrapper, styles.wrapperFirst]
}
_renderItem = ({item, index}) => (
<View style={this.wrapperStyle(index)}>
<SearchCard
search={item}
id={item.id}
filterAttributes={this.props.filterAttributes}
onSearch={this.props.onSearch}
onFavorite={this.props.onFavorite}
favorites={this.props.favorites}
/>
</View>
)
render () {
const { searches, filterAttributes, onSearch, onFavorite, favorites } = this.props
return (
<FlatList
data={searches}
extraData={{ filterAttributes: filterAttributes, onSearch: onSearch, onFavorite: onFavorite, favorites: favorites, searches: searches }}
keyExtractor={item => item.id}
renderItem={this._renderItem}
enableEmptySections
style={{backgroundColor: 'red'}}
/>
)
}
}
const styles = StyleSheet.create({
wrapperFirst: {
marginTop: 20
},
listItemWrapper: {
marginLeft: 20,
marginRight: 20,
marginBottom: 20
}
})
After hours of struggling, I find that adding a height to the item solved the problem.
It could be an issue related to the styles of StyleSheet applied to either the row or the FlatList itself. In my case I applied a wrong style property to the FlatList which in return did not display the list.