React native Flatlist items with negative margin - react-native

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

Related

Flatlist: A VirtualizedList contains a cell which itself contains more than one VirtualizedList of the same orientation as the parent list

I am getting the following error in my implementation of FlatList
A VirtualizedList contains a cell with more than one VirtualizedList of the same orientation as the parent list. You must pass a unique listKey prop to each sibling list.
VirtualizedList trace:
Child (vertical):
listKey: rootList-header
cellKey: rootList-header
Parent (vertical):
listKey: function listKey(item, index) {
return 'createEX' + index.toString();
}
cellKey: rootList
I am also adding relevant code. The code works when I test using expo, although the error comes up. The app crashes in TestFlight.
This is my FlatList Implementation. If I remove the MetNeedsMenu part, the code works.
<FlatList style={stylesCreate.scrollStyle}
listKey={(item, index) => {return 'createEX' + index.toString()}}
ListHeaderComponent={
<View style={stylesCreate.createForm}>
<Text style={styles.screenTitle}>CREATE NEW{"\n"}ENTRY</Text>
<Text style={stylesCreate.label}>How are you feeling now?</Text>
<Dropdown mood={newMoodBefore} setMood={setMoodBefore}/>
<Text style={stylesCreate.label}>Select the areas of life that made you feel grateful.</Text>
<MetNeedsMenu selectedNeeds={needs} setSelectedNeeds={setNeeds}/>
<InfoButton/>
<Text style={stylesCreate.label}>What are you grateful for?</Text>
<GratitudeInput value={newEntry} placeholder="Write Here..." setValue={setEntry}/>
<Text style={stylesCreate.label}>How are you feeling after doing the activity?</Text>
<Dropdown mood={newMoodAfter} setMood={setMoodAfter}/>
<CustomButton text='CREATE' onPress={addEntry}></CustomButton>
</View>
}
>
</FlatList>
This is my MetNeedsMenu implementation.
const K_OPTIONS = [
{
item: 'Physical Wellbeing:',
id: '1',
},
{
item: 'Peace & Calm',
id: '2',
},
{
item: 'Energizing Moments',
id: '3',
},
{
item: 'Engagement / Flow',
id: '4',
},
{
item: 'Connection',
id: '5',
},
{
item: 'Accomplishment',
id: '6',
},
{
item: 'Meaning / Fulfillment',
id: '7',
},
{
item: 'Others',
id: '8',
}
]
const MetNeedsMenu = ({ selectedNeeds, setSelectedNeeds }) => {
return (
<View style={{ marginBottom: 20}}>
<SelectBox
width={300}
label="Select multiple"
options={K_OPTIONS}
selectedValues={selectedNeeds}
onMultiSelect={onMultiChange()}
onTapClose={onMultiChange()}
hideInputFilter={true}
isMulti
listKey={(item, index) => {return index.toString()}}
labelStyle={{
fontSize: 15,
color: '#0060ff',
}}
containerStyle={{
backgroundColor: '#ffffff',
borderRadius: 5,
}}
arrowIconColor={'blue'} //style for dropdown arrow
optionsLabelStyle={{ //style for labels of options
color: 'black',
paddingLeft: 10,
fontSize: 15,
}}
multiOptionContainerStyle={{ //style multiple selections
backgroundColor: '#0060ff',
}}
multiListEmptyLabelStyle={{ //style for SELECT text
paddingLeft: 10,
}}
toggleIconColor={'#0060ff'} //style color of select icon
selectedItemStyle={{
paddingLeft: 10,
}}
/>
</View>
)
function onMultiChange() {
return (item) => setSelectedNeeds(xorBy(selectedNeeds, [item], 'id'))
}
}
I am guessing the problem lies with MultiNeedsMenu, but I have put listKey there with no success
The error tells you that you have a FlatList or Scrollview inside another FlatList or Scrollview with the same orientation. What is possible to do is to have nested FlatList or ScrollView of different orientation. Here is an example:
<FlatList
data={...}
keyExtracto={...}>
<ScrollView horizontal>
<Text>One</Text>
<Text>Two</Text>
</ScrollView>
</FlatList>

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

React Native: FlatList is not showing

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

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

Horizontal FlatList React Native with different height of items

I have a little problem with the FlatList Component in react native. In the FlatList are items with different height. Here is a little example of what i have:
example FlatL
The first problem is that all items are positioned at the top. I want to position all items at the bottom.
The second problem is that the height of the FlatList is always the height of the biggest item. So you can also scroll to another item in the white area of a small item...
here my code:
import React from "react";
import {
Text,
View,
Dimensions,
StyleSheet,
ListView,
TouchableOpacity,
Animated,
Image,
FlatList
} from "react-native";
import glamorous, { ThemeProvider } from "glamorous-native";
import theme from "../theme";
const { height, width } = Dimensions.get("window");
const cards = [
{
id: 1,
color: "red",
height: 400
},
{
id: 2,
color: "blue",
height: 300
},
{
id: 3,
color: "yellow",
height: 200
}
];
class Test extends React.Component {
render() {
return (
<View style={{ bottom: 0 }}>
<FlatList
ref={elm => (this.flatList = elm)}
showsHorizontalScrollIndicator={false}
data={cards}
pagingEnabled={true}
horizontal={true}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View
style={{
height: item.height,
width: width,
backgroundColor: item.color
}}
/>
)}
/>
</View>
);
}
}
export default Test;
Has anyone a solution?
You can change the position of each item by setting the contentContainerStyle property of the FlatList itself. But the height of the FlatList will always have to be the height of the largest component inside it.
I solved this with onViewableItemsChanged prop
viewabilityConfig={{itemVisiblePercentThreshold: 50}}
onViewableItemsChanged={this.onViewableItemsChanged}
onViewableItemsChanged = ({ viewableItems }) => {
let index = viewableItems[0].index;
this.setState({ indexOfImages: index, heightOfImages: SCREEN_WIDTH * (viewableItems[0].item.height / viewableItems[0].item.width) });
}
We have width and height of the items in its data so I'm taking width and height of the visible item on the screen and do some calculations and I'm using this.state.heightOfImages in flatlist height like this
height: this.state.heightOfImages == undefined ? SCREEN_WIDTH * (images[0].height / images[0].width) : this.state.heightOfImages
You can just wrap the content of each card in another <View> element with styles={{ minHeight: 200, maxHeight: 300 }} and it will work fine!