React Native: FlatList is not showing - react-native

I am trying to make a custom component that will display options for a new choice if the first choice is not clear enough. I am using the FlatList component to display the data and it seems to not be displaying the data that is given as a prop.
This is the render function for the component
import { Header, List, ListItem } from "react-native-elements";
import PickerBox from "./PickerBox";
render() {
return (
<View>
<Header
centerComponent={{
text: "By " + this.state.newTaxon + ", did you mean...",
style: { color: "white", fontSize: 20, textAlign: "center" }
}}
backgroundColor="black"
/>
<FlatList
data = {this.state.dataSource}
renderItem = {({item}) => {
<PickerBox
title = {item.c_syn_name}
/>
}}
keyExtractor = {(item) => item.c_syn_name}
/>
</View>
);
}
This is the PickerBox component
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "row",
padding: 10,
marginLeft: 16,
marginRight: 16,
marginTop: 8,
marginBottom: 8,
borderRadius: 5,
backgroundColor: "#FFF",
elevation: 2
},
title: {
fontSize: 16,
color: "#000"
},
container_text: {
flex: 1,
flexDirection: "column",
marginLeft: 12,
justifyContent: "center"
},
description: {
fontSize: 11,
fontStyle: "italic"
}
});
const PickerBox = (title) => {
return (
<View style={styles.container}>
<Text style={styles.container_text}>{title}</Text>
</View>
);
};
export default PickerBox;
This is the import statement for the PickerBox in the component
import PickerBox from "./PickerBox"; // reside in same folder
The dataSource state comes from a JSON object that contains a layout like this in each entry.
"c_node_name_scientific": "Centurio",
"c_syn_name": "wrinkle-faced bat",
"i_node_id": 27644,
The Output in the simulator is just the header, but the expected output is the header with the list underneath.

Firstly, You need to make sure that if your renderItem method uses a fat arrow function with curly braces like you are in your example, you need to add a return statement like so:
renderItem={({item}) => { return <PickerBox title={item.c_syn_name} /> }}
If you don't use curly braces you can define the function like this:
renderItem={({item}) => <PickerBox title={item.c_syn_name} />}
Secondly, make sure that the data is an array, not an object.
As per the description of the FlatList's data prop in the react-native documentation:
For simplicity, data is just a plain array. If you want to use something else, like an immutable list, use the underlying VirtualizedList directly.
From your question it seems as if you're wanting to loop through an array of objects similar to this:
[
{
"c_node_name_scientific": "Centurio",
"c_syn_name": "wrinkle-faced bat",
"i_node_id": 27644
},
{
"c_node_name_scientific": "xxx",
"c_syn_name": "xxx",
"i_node_id": 123
},
//...
]
If this is the case, just wrap the state's dataSource object in an array as demonstrated above.
If you're wanting to pass in the data as an object similar to this:
{
key1: {title: 'Title 1'},
key2: {title: 'Title 2'}
key3: {title: 'Title 3'}
}
you would need to do something like the following to make the data accessible to the FlatList:
<FlatList
data={Object.keys(this.state.dataSource)} // will result in ["key1", "key2", "key3"]
renderItem={({item}) =>
// here `item` will be the Object's key. eg: "key1"
<PickerBox title={this.state.dataSource[item].title} />
}
/>
And finally, if the Flatlist needs to update as State updates, you need to add in the prop extraData={this.state} to the FlatList. As per the FlatList Documentation:
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.

For me, the problem was that the parent element had flex: 1.
Removing it solved my problem

you can try this
renderItem = {({item}) => {
PickerBox(item.c_syn_name);
}}

First ,please make sure this.state.dataSourceis not an empty array.
If your dataSource is something like this, then this should work :
<FlatList
data={[{c_syn_name: 'a'}, {c_syn_name: 'b'}]}
keyExtractor = {item => item.c_syn_name}
renderItem={({item}) =><PickerBox title = {item.c_syn_name} />}
/>

Related

Add data to begining of FlatList without changing position

I'm trying to implement an "onBeginReached" like props in flatlist. I would like to append some data at the begining of my data array in a transparent way to user.
So using this flatList :
const App = () => {
const flatListRef = useRef(null);
const [data, setData] = useState(generateData(20));
const renderItem = ({ item }) => {
console.log(item);
return (
<View style={styles.itemContainer}>
<Text style={styles.itemText}>{item}</Text>
</View>
);
};
const handleMomentumScroll = (event) => {
console.log("Momentum end")
const xOffset = event.nativeEvent.contentOffset.x;
const index = Math.round(xOffset / 30);
if (index < 1) {
setData([-10 ,-9, -8, -7, -6,-5, -3, -2, -1, ...data]);
}
};
return (
<FlatList
style={{ width: 200, alignSelf: 'center', marginTop: 150 }}
initialScrollIndex={10}
horizontal
data={data}
snapToAlignment={'start'}
decelerationRate={'fast'}
snapToInterval={30}
getItemLayout={(data, index) => ({
length: 30,
offset: 30 * index,
index,
})}
keyExtractor={(item, index) => index.toString()}
renderItem={renderItem}
onMomentumScrollEnd={handleMomentumScroll}
/>
);
};
const styles = StyleSheet.create({
itemContainer: {
alignItems: 'center',
justifyContent: 'center',
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: 'blue',
},
itemText: {
color: 'white',
},
});
(https://snack.expo.io/GUblotbZc)
If I scroll to the index 0, it'll unshift my new data to my data array. But, it'll scroll automatically to the first index of the new data array. I would like to keep the current position when unshifting new data to the array.
There is a way to impletement that behaviour ?
here is demo: https://snack.expo.io/#nomi9995/flatlisttest
use maintainVisibleContentPosition props for preventing auto scroll in IOS but unfortunately, it's not working on android but good news is pull request has come for android and need to merge with react native.
<FlatList
ref={(ref) => { this.chatFlatList = ref; }}
style={styles.flatList}
data={this.state.items}
renderItem={this._renderItem}
maintainVisibleContentPosition={{
minIndexForVisible: 0,
}}
/>
The way I did it is by inverting FlatList using the inverted prop, and also reversing my list. In this way, the top of FlatList will be at the bottom with last item in my array is visible there.
When user scrolls to the top onEndReached is triggered, and I add new items to the beginning of my array and they will be added to the top of FlatList with out changing the current visible item at the top.
You can use to Flatlist prop named "ListHeaderComponent" to add any component at the beginning of your Flatlist. https://reactnative.dev/docs/flatlist#listheadercomponent

React native Flatlist items with negative margin

I have a simple vertical Flatlist on Android and I would like to render some of its items with a negative margin. The goal is for those items to appear wider than the Flatlist.
Something like this with red being an item of the flatlist:
Unfortunately, the edges of the items are cut by the edge of the Flatlist.
Is there a way to display items that are wider than the Flatlist that renders them?
EDIT:
I know I can achieve the visual on my illustration by adding a margin/padding to every item on the list except the red one. What I would like to know is if it is possible to make a specific item wider than the Flatlist itself (not just wider than the other items)
I would rather stylize the one item that needs to be wider, rather than all the others.
I myself actually never make an item in FlatList with wider scale of the whole flatList cause I know it's a bad idea and it tends to be ugly looking, just imagine an item with an overlapped verticalScrollIndicator. But the better way is that you can add a View like this.
...FlatList tag.....
renderItem={({ item, index }) => {
return (
<View style={{ paddingHorizontal:item.isWider ? 0 : '5%' }}>
....Children
</View>
)
}}
You can also write the statement to work only for some particular item index.
By the way, the scroll indicator will not overlap with any item and it has a controlled and better looking UI.
Whenever you want to render specific items without margin value, the only thing you have to do is override style.
I create a sample application according to your requirements.
import React, { Component } from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text } from 'react-native';
const DATA = [
{
id: 1,
title: 'First Item',
},
{
id: 2,
title: 'Second Item',
},
{
id: 3,
title: 'Third Item',
},
{
id: 4,
title: 'Forth Item',
},
{
id: 5,
title: 'Fifth Item',
},
{
id: 6,
title: 'Sixth Item',
},
];
export default class App extends Component {
renderItems = ({ item }) => (
// Suppose if you want to change margin value & background color of id == 3
<View style={item.id !== 3 ? styles.item : [styles.item, { backgroundColor: 'red', margin: 0 }]}>
<Text>{item.title}</Text>
</View>
)
render() {
return (
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={this.renderItems}
keyExtractor={item => item.id}
/>
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20,
},
item: {
backgroundColor: 'green',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
});
Hope this helps you. Feel free for doubts.
According to the image you provided, and only style the specific list item, I could change align-self, width, and negative margin to achieve what you want.
Apply the specific item
<FlatList
contentContainerStyle={styles.list}
data={data}
renderItem={({ item, index }) => (
<View style={[styles.item, index === 3 && styles.oversize]} />
)}
/>
styles:
oversize: {
backgroundColor: 'orange',
alignSelf: 'stretch',
width: 'auto',
marginHorizontal: -16,
},
Here is working example

React-native : show content on two rows

I want to show 6 options per row instead of showing only one option per row.
I'm still new in react-native
Here is the code :
import React from 'react';
import {fromJS} from 'immutable';
import {ScrollView, StyleSheet, TouchableOpacity} from 'react-native';
import {Text} from 'src/components';
import Option from './OptionVariable';
import {checkOption} from 'src/modules/product/helper';
import {margin} from 'src/components/config/spacing';
class AttributeVariable extends React.Component {
onSelectOption = (option) => {
const {onSelectAttribute, attribute} = this.props;
onSelectAttribute(attribute.get('id'), attribute.get('name'), option.get('option'));
};
render() {
const {attribute, meta_data, variations} = this.props;
// Attribute selected
const attributeSelected = meta_data.find(attr => attr.get('id') === attribute.get('id') && attr.get('name') === attribute.get('name'));
return (
<>
<Text>
{attribute.get('name')}: <Text colorSecondary>{attributeSelected ? attributeSelected.get('option') : ''}</Text>
</Text>
<ScrollView
style={styles.attribute}
vertical
showsHorizontalScrollIndicator={false}>
{attribute.get('options').map(option => {
const disabled = meta_data.size === 0 ? false : !checkOption(variations, meta_data, fromJS({
id: attribute.get('id'),
name: attribute.get('name'),
option: option.get('option'),
}));
return (
<TouchableOpacity
activeOpacity={disabled ? 1 : 0}
key={option}
onPress={() => disabled ? {} : this.onSelectOption(option)}
>
<Option
type={attribute.get('type')}
selected={attributeSelected && option.get('option') === attributeSelected.get('option')}
disabled={disabled}
option={option}
/>
</TouchableOpacity>
)
})}
</ScrollView>
</>
);
}
}
const styles = StyleSheet.create({
attribute: {
marginTop: margin.small,
},
});
export default AttributeVariable;
Here is how it looks :
I hope everything is clear to you.
Sorry for my bad english it's not my main language....
Thanks in advance for any help.
you can say in your attribute style:
attribute: {
marginTop: margin.small,
flexDirection: 'row'
},
so it should align in rows instead of columns
That should help you to learn more about styling components in react native: https://facebook.github.io/react-native/docs/flexbox
Try using the Flatlist (instead of ScrollView) component passing the horizontal={true} props.
Like this:
FlatList
horizontal={true}
data={attribute.option}
renderItem={({ item }) => "Pass your component"}
keyExtractor={item => item.id}
/>
UPDATE 2 :
I used View instead of ScrollView with the following styles and it worked just fine..
<View style={[styles.container, ]}
horizontal
style={{AlignContent: 'flex-start', flexDirection: 'row', alignItems: 'flex-start',}}
style={styles.attribute}
showsHorizontalScrollIndicator={false}>
UPDATE 1 :
I solved the problem using this :
container: {
marginTop: margin.small,
flexDirection: 'row-reverse',
position: 'relative',
width: 'auto',
alignSelf: 'stretch',
maxWidth: 29,
height: 29,
marginBottom: margin.big,
borderRadius: borderRadius.base,
borderWidth: 1,
},
I changed the size of option so all of them appear on the same row (not exactly what I want but it works)
If anyone have a better answer please share.

React Native FlatList rendering a few items at a time

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

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>
}
});