React Native FlatList rendering a few items at a time - react-native

I have a list of chat messages in my app to which new items are added to the bottom. I used some code from another SO question to make the FlatList stick to the bottom when new items are added, as below
<FlatList
data={messages}
renderItem={({item}) => <ChatMessage message={item}></ChatMessage>}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={messages.length}
initialScrollIndex={messages.length-1}
ref={ref => this.flatList = ref}
onContentSizeChange={(contentWidth, contentHeight)=>{
this.flatList.scrollToEnd();
}}
/>
The problem is that when the initial list renders (only 35 items, hardcoded in an array for now) it seems to render just a few items, then scroll down a bit, then render a few more, then scroll down a bit until it finally completes the rendering and sticks to the bottom. It's choppy and slow, despite adding initialNumToRender={messages.length} and rendering an incredibly simple node for each result.
Ideally I guess I need to wait for it to fully render before displaying anything to the user but (A) they'd have to wait a couple of seconds to start using the chat room and (B) I don't think that's how Flatlist works, I assume the elements have to be viewable before it is rendered.
Is there just a better way to do this? (Testing on Android by the way)
EDIT: Adding ChatMessage component for completeness
// Chat Message
import React, { Component } from 'react'
import {
StyleSheet,
ImageBackground,
Text,
View
} from 'react-native'
class ChatMessage extends Component {
constructor(props) {
super(props)
this.state = { }
}
render() {
return (
<View style={styles.chatMessage}>
<View style={styles.chatMessage_layout}>
<View style={styles.chatMessage_pic}>
<View style={styles.chatMessage_pic_image}>
<ImageBackground
source={require('./assets/images/profile-pics/example-profilr.png')}
style={styles.chatMessage_pic_image_background}
imageStyle={{ borderRadius: 40/2 }}
resizeMode="cover"
>
</ImageBackground>
</View>
</View>
<View style={styles.chatMessage_details}>
<View style={styles.chatMessage_name}>
<Text style={styles.chatMessage_name_text}>
{this.props.message.name}
<Text style={styles.chatMessage_name_time}> 24h</Text>
</Text>
</View>
<View style={styles.chatMessage_message}>
<Text style={styles.chatMessage_message_text}>{this.props.message.text}</Text>
</View>
</View>
</View>
</View>
)
}
}
export default ChatMessage;
const styles = StyleSheet.create({
chatMessage: {
paddingVertical: 10,
paddingHorizontal: 24
},
chatMessage_layout: {
flexDirection: 'row'
},
chatMessage_pic: {
width: 40,
height: 40,
marginRight: 12
},
chatMessage_pic_image: {
width: 40,
height: 40
},
chatMessage_pic_image_background: {
width: 40,
height: 40
},
chatMessage_details: {
flex: 1
},
chatMessage_name_text: {
color: '#FFF',
fontSize: 14,
fontWeight: 'bold'
},
chatMessage_name_time: {
fontSize: 11,
color: 'rgba(255,255,255,0.6)'
},
chatMessage_message: {
flexDirection: 'row',
alignItems: 'center'
},
chatMessage_message_text: {
color: '#FFF',
fontSize: 12
}
})

If you have less number of items and want to render all items at once then you should use ScrollView as mentioned in the docs
ScrollView: Renders all elements at once, but slow if there are large number of elements.
FlatList: Renders items in a lazy mode, when they are about to appear and removes them when they leave the visible display to save memory that makes it usable for performance on large lists.
For Flatlist optimization you need to use PureComponent whenever you render the child so that it only shallow compares the props.
Also in the keyExtractor use a unique id for your item and do not depend upon the index, since when the item updates the index is not reliable and may change

Related

How to display a button at the bottom of a Webview in react-native?

Inside my component (PrivacyPolicy.js), i have a header view, a webview, and a footer view. the webview, depending on the size, gets scrollable. my issue is that the footer view is displayed at the bottom of the screen like if its style was "position: 'absolute'" so it keeps displayed while scrolling. I need to have it after all webview is displayed.
<View style={styles.main_container}>
<View style={styles.header_container}>
...
</View>
<WebView originWhitelist={['*']} source={{ html: privacyPolicyContent }}/>
<View style={styles.footer_container}>
<CheckBox
disabled={false}
value={this.state.isChecked}
onValueChange={(newValue) => this.setState({
isChecked: newValue
})}
style={styles.checkbox}
tintColors={{ true: '#157dfa' }}
/>
<Text style={styles.checkbox_text}>I have read and accept the Privacy Polic</Text>
</View>
</View>
My styles:
const styles = StyleSheet.create({
main_container: {
flex: 1,
paddingHorizontal:'5%'
},
header_container: {
height: scale(90),
flexDirection: 'row',
marginLeft: 10
},
checkbox_container: {
flexDirection: 'row'
},
checkbox: {
marginLeft: -5,
},
checkbox_text: {
marginTop: 8,
fontSize: 10
}
})
I can see few suggestions:
Since your button is a React Native Button => You can show/hide based on the scrollY positions. For that, you need to communicate over the Bridge to dispatch an event accordingly.
As an alternative solution => You can create the button on the Webview its self to have the same functionality.

React Native pseudo leaking memory after remove elements from screen (ios)

I'm experiencing some out of memory crashes in production. Trying to isolate the problem I could make a small app to reproduce the issue.
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
const { count } = this.state;
const extraContent = new Array(200 * count).fill().map((_, index) => (
<View key={index}><Text>Line {index}</Text></View>
));
return (
<View style={styles.container}>
<View style={styles.actions}>
<TouchableOpacity onPress={() => this.setState({ count: count + 1})}>
<View style={styles.button}>
<Text style={styles.buttonText}>Add</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => count > 0 && this.setState({ count: count - 1})}>
<View style={styles.button}>
<Text style={styles.buttonText}>Remove</Text>
</View>
</TouchableOpacity>
</View>
<View>
<Text>Current count: {count}</Text>
<View>{extraContent}</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
width: '100%',
},
actions: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
},
buttonText: {
color: '#ffffff',
},
button: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#95afe5',
height: 50,
width: 100,
marginBottom: 5,
borderRadius: 5,
},
});
The code adds and removes some views from the screen when you press and or remove. It's expected that pressing Add three times and then Remove three times one would end with the same amount of memory use. What's really happening is that some of the memory is not released as shown in the graph:
It's interesting that adding again the three times and removing three times the peak of memory consumption is lower than the first round and there's no leak, but if we change to add/remove five times there's an extra pseudo leak.
I call it pseudo leak because from time to time, could understand why, a good portion of this retained memory is released, but it never comes back to the original baseline. It makes me believe that this effect may not be an actual leak, but some kind of cache instead.
In my production app this effect has reached 150+ MB leading to OOM crashes on devices with 1GB of RAM.
Does any one know what is it and if there's a way to avoid this behavior?
Perhaps your onPress props are implicitly returning data unnecessarily. What happens if you put {} after the arrow to prevent a return. Does it make any difference?
You may be able to do something for your setState as well. Consider trying: setState(() => {…code})
This might also be helpful:
When to use React setState callback
Sorry, this is more of a comment, but I wanted to add in some code blocks to help communicate. I think your example is defeating some of React's caching by creating the elements each time inside the render function. Is the result any different if the elements in extraContent are more stable? One way to do this is below:
Create extraContent outside of render, store it in state, and make the remove function return a slice of the array. Something like:
// in constructor
this.state = {
extraContent: [],
};
// in component
add() {
const totalElements = this.state.extraContent.length;
this.setState({ extraContent: this.state.extraContent.concat(new Array(200).fill().map((_, index) => (
<View key={totalElements + index}><Text>Line {totalElements + index}</Text></View>
))
});
}
remove() {
const totalElements = this.state.extraContent.length;
if (totalElements > 0) {
this.setState({ extraContent: this.state.extraContent.slice(0, totalElements - 200) });
}
}
render() {
// cut out everything before the return
// use add and remove functions in your TouchableOpacity components
}
I'm mentioning this because it's possible the problem you've identified in your example is different from the one in your real app, unless you are employing the same strategy for creating components there.

React Native Scroll View and Flex Children

I am having a lot of trouble working with a scroll view and flex dimensions. Basically the initial screen of the app should show a top side "banner" while the bottom is the "content".
The content is going to contain many "cards" that each contain information, and users will be able to scroll down to see the next card, and the next, and etc.
However, the flex property does not seem to be working at all, and I am not even able to create the proper sizes for the "banner" section and the "content" section.
This is my code:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Image,
ScrollView,
TextInput
{ from 'react-native';
import RestaurantCard from './common/RestaurantCard';
export default class MemberArea extends Component<Props> {
constructor(props){
super(props);
this.state = {
searchText: ''
}
}
render() {
return (
<ScrollView contentContainerStyle = {styles.contentContainer}>
<View style={styles.banner}>
<TextInput style={styles.search}
underlineColorAndroid='rgba(0,0,0,0)'
placeholder="Search for Restaurants"
textAlign= 'center'
textAlignVertical='center'
returnKeyType="search"
/>
</View>
<Text style={styles.dropdown}>
{"What's nearby V"}
</Text>
<View style={styles.cards}>
<RestaurantCard />
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
contentContainer:{
flexGrow: 1,
flexDirection: 'column'
},
cards:{
flex:3,
backgroundColor:'#000000'
},
banner: {
flex:15,
backgroundColor: '#F36B45',
paddingBottom: 80
},
search:{
marginTop: 20,
paddingBottom: 5,
paddingTop: 5,
height: 30,
marginHorizontal: 40,
borderRadius: 20,
backgroundColor: 'rgb(255,255,255)'
},
dropdown:{
color: 'black',
fontWeight:'900',
fontSize:24,
margin:30
}
});
UPDATE: As seen in the comments below, I surrounded my scrollview with another view. I gave the Parent view Flex:1. The problem I run into now is as follows:
If I give the scrollview contentcontainerstyle flexGrow: 1, then the scrolling works as normal, but I cannot give the children sections of the scrollview space dimensions with flex values. However, if I give the scrollview flex:1 (rather than flexGrow), then I can give space dimensions to the children sections, but the scrolling function is disabled. Any help would be greatly appreciated!
Issue you have is you set content inside memberArea to grow according to the content but outside the memberArea, it's fixed. Add flex: 1 style to View in App.
<View style={{flex: 1}}> // App
Then you don't need to set contentContainerStyle for the ScrollView
Why do you set flex: 15? Check the doc first.
Here's the codesandbox
Read the React-Native Docs on Flexbox properly.
[https://facebook.github.io/react-native/docs/flexbox.html][1]
You cannot set Higher values to child Views of the Parent Views.
If you want to divide the Parent's Flex: 1, into 2 children - you can do Flex:0.5 and Flex:0.5 to the two children.
If you want every child to have their own space, assign Flex: 1 to each of them.
Check the Docs and other articles on it for more details.

React Native: how to simulate onBlur and onFocus event for View

In React Native, only TextInput has onFocus and onBlur event listener. However, we would like to simulate this effect on a View.
Here is what we try to achieve. There is a View on the screen. It gains "focus" when user taps on it and is hightlighted. We want to detect that user taps outside the View, so that we can "blur" the View, that is, remove the highlight.
I know we can use focus method (https://facebook.github.io/react-native/docs/nativemethodsmixin.html#focus) to request focus for the View. The problem is that I don't know how to detect that user presses outside the View.
I think the easiest is to set a state variable instead so you can play with that one such as:
class MyView extends Component {
constructor(props) {
super(props);
this.state = {
activeView: null,
}
this.views = [
(<View style={{ padding: 20 }}><Text>View 1</Text></View>),
(<View style={{ padding: 20 }}><Text>View 2</Text></View>),
(<View style={{ padding: 20 }}><Text>View 3</Text></View>),
(<View style={{ padding: 20 }}><Text>View 4</Text></View>),
(<View style={{ padding: 20 }}><Text>View 5</Text></View>),
];
}
_setActive( index ) {
return () => {
this.setState({ activeView: index })
}
}
render() {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
{this.views.map( (view, index) => {
let containerStyle = [];
if ( index === this.state.activeView ) {
containerStyle.push({ borderWidth: 10 })
}
return (
<TouchableOpacity onPress={this._setActive(index)} style={containerStyle}>
{view}
</TouchableOpacity>
);
})}
</View>
);
}
}
You can use MyView where requried as <MyView /> also every item on the array views can have any style required and configure each if is active just by access to this.state.activeView.
Let me know if you have any question.
In situations like this, I add onPress() to the the element that is outside of the View in question.
<View onPress(removeHighlight)></View>
<View onPress(addHighlight)></View>

setNativeProps Change Value for Text Component React Native Direct Manipulation

I want to directly update the value of a component due to performance reasons.
render(){
<View>
<Text style={styles.welcome} ref={component => this._text = component}>
Some Text
</Text>
<TouchableHighlight underlayColor='#88D4F5'
style={styles.button}>
<View>
<Text style={styles.buttonText}
onPress={this.useNativePropsToUpdate.bind(this)}>
Iam the Child
</Text>
</View>
</TouchableHighlight>
</View>
}
This is the method I use to update the text component. I dont know if I am setting the right attribute/ how to figure out which attribute to set:
useNativePropsToUpdate(){
this._text.setNativeProps({text: 'Updated using native props'});
}
Essentially trying to follow the same approach from this example:
https://rnplay.org/plays/pOI9bA
Edit:
When I attempt to explicitly assign the updated value:
this._text.props.children = "updated";
( I know this this the proper way of doing things in RN ). I get the error "Cannot assign to read only property 'children' of object'#'"
So maybe this is why it cant be updated in RN for some reason ?
Instead of attempting to change the content of <Text> component. I just replaced with <TextInput editable={false} defaultValue={this.state.initValue} /> and kept the rest of the code the same. If anyone know how you can change the value of <Text> using setNativeProps OR other method of direct manipulations. Post the answer and ill review and accept.
The text tag doesn't have a text prop, so
this._text.setNativeProps({ text: 'XXXX' })
doesn't work.
But the text tag has a style prop, so
this._text.setNativeProps({ style: { color: 'red' } })
works.
We can't use setNativeProps on the Text component, instead, we can workaround and achieve the same result by using TextInput in place of Text.
By putting pointerEvent='none' on the enclosing View we are disabling click and hence we can't edit the TextInput (You can also set editable={false} in TextInput to disbale editing)
Demo - Timer (Count changes after every 1 second)
import React, {Component} from 'react';
import {TextInput, StyleSheet, View} from 'react-native';
class Demo extends Component {
componentDidMount() {
let count = 0;
setInterval(() => {
count++;
if (this.ref) {
this.ref.setNativeProps({text: count.toString()});
}
}, 1000);
}
render() {
return (
<View style={styles.container} pointerEvents={'none'}>
<TextInput
ref={ref => (this.ref = ref)}
defaultValue={'0'}
// editable={false}
style={styles.textInput}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 0.7,
justifyContent: 'center',
alignItems: 'center',
},
textInput: {
fontSize: 60,
width: '50%',
borderColor: 'grey',
borderWidth: 1,
aspectRatio: 1,
borderRadius: 8,
padding: 5,
textAlign: 'center',
},
});
export default Demo;
As setNativeProps not solving the purpose to alter the content of <Text />, I have used below approach and is working good. Create Simple React Component like below...
var Txt = React.createClass({
getInitialState:function(){
return {text:this.props.children};
},setText:function(txt){
this.setState({text:txt});
}
,
render:function(){
return <Text {...this.props}>{this.state.text}</Text>
}
});