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

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>`}} />

Related

React Native TextInput onPressOut fires instantly

I am making a class for a custom TextInput, where the style will change when the field is selected, and will change back as soon as it is pressed out of. It looks as follows...
export function SoftSearchBar({
height=40,
width='100%',
fontSize=20,
fireOnChange={function(){console.log("No Change Function in place")}},
value=false,
placeholder="Placeholder",
type=null
}){
const [isActive, setActive] = useState(false)
const [style, setStyle] = useState({})
useEffect(() => {
console.log(isActive)
if (isActive){
setStyle(style => ({style: styles.softSearchActive, width: width}))
}
else{
setStyle(style => ({style: styles.softSearchInactive, width: width}))
}
}, [isActive])
return(
<View style={{height: height, flexDirection: 'row'}}>
<TextInput
value={value}
onPressIn={() => setActive(true)}
onPressOut={() => setActive(false)}
style={{...style.style, width: width, zIndex: 0, fontSize: fontSize}}
textContentType={type}
text
placeholder={placeholder}
placeholderTextColor={'black'}
autoCorrect={false}
onChangeText={text => {
fireOnChange(text)
}}
/>
</View>
)
}
Almost all of this works as expected, when the field is pressed, an outline appears indicating its selection, and the text changes color. However, onPressOut fires immediately after onPressIn, as the log will look like this as soon as I press the field
true
false
indicating that onPressOut fired, since it is the only way to setIsActive(false)
I saw some solutions recommending using onResponderRelease as opposed to onPressOut but then it just never unselects. Is there some syntax Im missing with onPressOut? This seems like a pretty simple and straightforward syntax so I am unsure
Main Issue with your code is onPressIn and onPressOut you need to change them to onFocus and onBlur
Here is a working example you can paste into this website
https://reactnative.dev/docs/textinput
You can set your default Input style and then when active you can enable the style you want.
outlineStyle: none to get rid of the default blue outline of the textinput when focused
Can also just remove handleFocus & handleBlur and move the function into the actual function calls to reduce the code further
import React from "react";
import { SafeAreaView, StyleSheet, TextInput } from "react-native";
const UselessTextInput = () => {
const [style, setStyle] = React.useState({borderWidth:2 , borderColor: 'red', outlineStyle: 'none'});
const [active, setActive] = React.useState(false)
const handleFocus = () => setActive(true)
const handleBlur = () => setActive(false)
return (
<SafeAreaView>
<TextInput
style={[styles.input, active && style]}
onFocus={handleFocus}
onBlur={handleBlur}
onChangeText={() => {}}
value={null}
placeholder="useless placeholder"
keyboardType="numeric"
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
});
export default UselessTextInput;

React Native SectionList Performance Issue: Extremely Low JS Frame Rate While Scrolling React Native SectionList

Scrolling a SectionList results in unreasonably low JS frame rate dips (~30) for simple cell renders (a single text label) and gets much worse if cell contents are any more complex (several text labels in each cell will result in single digit JS frame rate dips).
The user facing-problem manifested by the low JS frame rate is very slow response times when a user taps anything after scrolling (it can be ~5 seconds delay).
Here's an example repo which creates both a FlatList and a SectionList each with 10,000 items on a single screen (split laterally across the middle of the screen). It's a managed Expo app (for ease of reproduction) and it's written in Typescript (because I'm used to that). The readme describes setup steps if you need them. Run the app on a physical device and turn on React Native's performance monitor to view the JS frame rate. Then scroll each of the lists as fast as you can to view the differing effects on the JS frame rate while scrolling a FlatList vs a SectionList.
Here's the entire source (from that repo's App.tsx file; 61 lines) if you prefer to bootstrap it yourself, or eyeball it here without going to GitHub:
import React from "react"
import { FlatList, SectionList, Text, View } from "react-native"
const listSize = 10_000
const listItems: string[] = Array.from(
Array(listSize).keys(),
).map((key: number) => key.toString())
const alwaysMemoize = () => true
const ListItem = React.memo(
({ title }: { title: string }) => (
<View style={{ height: 80 }}>
<Text style={{ width: "100%", textAlign: "center" }}>{title}</Text>
</View>
),
alwaysMemoize,
)
const flatListColor = "green"
const flatListItems = listItems
const sectionListColor = "blue"
const sectionListItems = listItems.map(listItem => ({
title: listItem,
data: [listItem],
}))
const approximateStatusBarHeight = 40
const App = () => {
const renderItem = React.useCallback(
({ item }: { item: string }) => <ListItem title={item} />,
[],
)
const renderSectionHeader = React.useCallback(
({ section: { title } }: { section: { title: string } }) => (
<ListItem title={title + " section header"} />
),
[],
)
return (
<View style={{ flex: 1, paddingTop: approximateStatusBarHeight }}>
<FlatList
style={{ flex: 1, backgroundColor: flatListColor }}
data={flatListItems}
renderItem={renderItem}
keyExtractor={item => item}
/>
<SectionList
style={{ flex: 1, backgroundColor: sectionListColor }}
sections={sectionListItems}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
keyExtractor={item => item}
/>
</View>
)
}
export default App
Have I missed some performance optimization/am I doing something wrong? Or is this React Native's expected performance?
Another question as a side note: If I don't set the height of each of the cells to something reasonably tall (eg 80 in the example above) and just let the height adjust to the height of the contained text (14pt I believe), the JS frame rate dip for both types of lists becomes quite bad; 20 JS frames or less.
I'll also note here that the performance tests shown in the screen shots were done on an iPhone 13 mini.

react native flat list how to force list items to be the same height?

I have a React-Native application where I am using FlatList to display a list of items obtained from the server. The list has 2 columns and I need my list items to be the same height. I put a border around the code rendering my list items but the list items are not the same height. I have tried using flexbox settings to make the view fill the container, but everything I try makes no difference.
I have created a simplified version of my app to illustrate the issue:
See that the red bordered areas are NOT the same height. I need to get these to be the same height.
The grey border is added in the view wrapping the component responsible for a list item and the red border is the root view of the component responsible for a list item. See the code below for clarity.
I can not use the grey border in my application because my application shows empty boxes whilst the component responsible for a list item is getting additional information from the server before it renders itself
Furthermore I can not used fixed sizes for heights.
Application Project structure and code
My code is split up in a manner where the files ending in "container.js" get the data from the server and pass it to its matching rendering component. For example, "MainListContainer" would be getting the list from the server and then pass the list data to "MainList", and "ListItemContainer" would get additional information about the single list item from the server and pass it to "ListItem" to render the actual item. I have kept this model in my simplified application so its as close to my real application as possible.
index.js
import {AppRegistry} from 'react-native';
import MainListContainer from './app/components/MainListContainer';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => MainListContainer);
MainListContainer.js
import React from 'react';
import MainList from './MainList';
const data = [
{id: '1', title: 'Item 1', subtitle: 'A', description: 'This is the first item.'},
{id: '2', title: 'Item 2', subtitle: 'B', description: 'The Big Brown Fox Jumped over the lazy dogs. The Big Brown Fox Jumped over the lazy dogs.',},
];
const MainListContainer = () => {
return ( <MainList items={data} /> );
};
export default MainListContainer;
MainList.js
import React from 'react';
import {StyleSheet, FlatList, View} from 'react-native';
import ListItemContainer from './ListItemContainer';
export default class MainList extends React.Component {
constructor(props) {
super(props);
this.state = { numColumns: 2};
this.renderItem = this.renderItem.bind(this);
}
renderItem({item, index}) {
return (
<View style={styles.flatListItemContainer}> <!-- THIS IS WHERE THE GREY BORDER IS ADDED -->
<ListItemContainer key={index} item={item} />
</View>
);
}
render() {
const {items} = this.props;
const {numColumns} = this.state;
return (
<View>
<FlatList
data={items}
renderItem={this.renderItem}
numColumns={numColumns}
key={numColumns}
keyExtractor={(item) => item.id}
/>
</View>
);
}
};
const styles = StyleSheet.create({
flatListItemContainer: {
flex: 1,
margin: 10,
borderColor: '#ccc',
borderWidth: 1,
},
});
ListItemContainer.js
import React from 'react';
import ListItem from './ListItem';
const ListItemContainer = (props) => {
const { item } = props;
return (
<ListItem item={item} />
);
};
export default ListItemContainer;
ListItem.js
import React from 'react';
import {TouchableHighlight, View, StyleSheet, Image, Text} from 'react-native';
const ListItem = (props) => {
const { item } = props;
return (
<TouchableHighlight
underlayColor="white"
>
<View style={styles.containerView}> <!-- THIS IS WHERE THE RED BORDER IS ADDED -->
<View style={styles.top_row}>
<Image style={styles.image} source={require('../images/placeholder.png')} />
<View style={styles.title_texts}>
<Text style={{fontWeight:'bold'}}>{item.title}</Text>
<Text style={{color: 'rgb(115, 115, 115)'}}>{item.subtitle}</Text>
</View>
</View>
<Text>{item.description}</Text>
</View>
</TouchableHighlight>
);
};
export default ListItem;
const styles = StyleSheet.create({
containerView: {
padding: 14,
borderColor: 'red',
borderWidth: 1,
},
top_row: {
flex: 1,
flexDirection: 'row',
marginBottom: 10,
},
title_texts: {
flex: 1,
flexDirection: 'column',
},
image: {
alignSelf: 'flex-end',
resizeMode: 'cover',
height: 40,
width: 40,
marginRight: 20
},
});
What I have tried
ListItem.js : move the style onto the "TouchableHighlight" view
ListItem.js : add a view wrapping "TouchableHighlight" view and adding style there
ListItem.js : added "alignItems:'stretch' on the "TouchableHighlight, added it to the "containerView" style, tried it on the description field too
same as "alignItems" but used "alignedSelf" instead
same as "alignItems" but used "alignedContent" instead
tried using "flexGrow" on different views (container, description)
You can measure the height of every element in the list and when you determine the maximum height, you can use that height for every element in the list.
const Parent = ({ ...props }) => {
const [maxHeight, setMaxHeight] = useState<number>(0);
const computeMaxHeight = (h: number) => {
if (h > maxHeight) setMaxHeight(h);
}
return (
<FlatList
data={props.data}
renderItem={({ item }) => (
<RenderItem
item={item}
computeHeight={(h) => computeMaxHeight(h)}
height={maxHeight}
/>
)}
....
/>
)
}
The Items:
const RenderItem = ({...props }) => {
return (
<View
style={{ height: props.height }}
onLayout={(event) => props.computeHeight(event.nativeEvent.layout.height)}
>
<Stuffs />
</View>
)
}
This is a very non-performant way of achieving this. I would avoid this if I have a long list or any list of more than a few items. You however can put certain checks in place to limit rerendering etc. Or alternatively if it is only text that will affect the height, then you can only measure the height of the element with the most text and use that element's height for the rest.
Instead of set fixed width height, you can use flex box to achieve it. I just solved the issue by removing alignSelf at the FlatList and add alignItems center on it.
Wrap the flatList in flex box with align item center, you can add the code in your MainList.js file, the first <View>, i.e:
render() {
const {items} = this.props;
const {numColumns} = this.state;
return (
<View style={{flex: 1, alignItems: 'center'>
<FlatList
data={items}
renderItem={this.renderItem}
numColumns={numColumns}
key={numColumns}
keyExtractor={(item) => item.id}
/>
</View>
);
If still not reflected, you may try to add flex:1, alignItems center in FlatList style props.
You are missing a very basic concept of giving fixed height to the flatlist items, in your ListItem.js, try to set height:200 in containerView. Let me know if that works for you

Global or static State for flatlist Rows in react native

Is it possible that for all the rows of Flatlist there is one static state.
I have a Component which i want to display only on the Flatlist's one row when i click on it. And when i click on other row of Flatlist The Component should not render on previous row but on current clicked row.
Here I have two Rows of Flatlist. I want To render Progress bar only for the clicked cardview or Play Buton.
So my logic is when i click the Button Then I somehow put the keyID of clicked Flatlist's rows in the global state which will only render the clicked Card View's progress bar.
Somehow Like this code:
{ GlobalState===this.props.key && (<ProgressBar />)}
as a checker for each Row render in flatlist
I ended up using global variable in component and prop of component.
In renderItem of Flatlist:
function in renderItem of Flatlist
_play = () => {
this.setState({ playState: "playing" });
TrackPlayer.play();
const thisplaying = this.props.item.Key;
ProgressBar.setCurrentPlayer(thisplaying);//Here I call to put the Key of Item to the component's global variable
};
using component in render of renderItem of Flatlist
<ProgressBar PlayerKey={this.props.item.Key} />
In Component{ ProgressBar.js } used in renderItem of Flatlist:
import React from "react";
import { View, Slider, Text} from "react-native";
import TrackPlayer, { ProgressComponent } from "react-native-track-player";
import { formatTime } from "./utils";
global.MineKey = null; //Here I used Global Variable
class ProgressBar extends ProgressComponent {
static setCurrentPlayer = player => {
global.MineKey = player; //Setting global variable on every call from the cardview upon play
};
onSliderEditStart = () => {
TrackPlayer.pause();
};
onSliderEditEnd = () => {
TrackPlayer.play();
};
onSliderEditing = value => {
TrackPlayer.seekTo(value);
};
render() {
const position = formatTime(Math.floor(this.state.position));
const duration = formatTime(Math.floor(this.state.duration));
return global.MineKey == this.props.PlayerKey ? ( //If Global variable key is equal to the Key of RowItem of Flatlist
<View style={{ flexDirection: "row",alignItems: "center", flex: 1,width: "100%"}}>
<Text style={{ color: "white", alignSelf: "center" }}>{position}</Text>
<Slider
onTouchStart={this.onSliderEditStart}
onTouchEnd={this.onSliderEditEnd}
onValueChange={this.onSliderEditing}
value={this.state.position}
maximumValue={this.state.duration}
maximumTrackTintColor="gray"
minimumTrackTintColor="white"
thumbTintColor="white"
style={{
flex: 1,
alignSelf: "center"
}}
/>
<Text style={{ color: "white", alignSelf: "center" }}>{duration}</Text>
</View>
) : null;
}
}
module.exports = ProgressBar;

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.