Flatlist won't scroll; cells rendering with extra space - react-native

I'm having this very strange problem. When I render a list of products with a FlatList, it's putting this giant space between my cells. (I've commented out the background image to speed loading, but it behaves the same either way)
ProductsListScreen.js
class ProductsListScreen extends Component<Props> {
render() {
return <WithFlatList products={this.props.products} />;
// return <WithMap products={this.props.products} />;
}
}
export default connect(({ productsReducer }) => ({
products: Object.values(productsReducer.products)
}))(ProductsListScreen);
const WithFlatList = ({ products }) => {
return (
<FlatList
data={products}
renderItem={({ item }) => <ProductListCellView product={item} />}
keyExtractor={item => `${item.id}`}
/>
);
};
const WithMap = ({ products }) => {
return (
<ScrollView contentContainerStyle={styles.container}>
{products.map(p => (
<ProductListCellView product={p} key={p.id} />
))}
</ScrollView>
);
};
const styles = {
container: {
flex: 1,
height: "100%"
}
};
ProductsListCellView.js
const ProductListCellView = ({ product }: Props) => {
return (
<View style={styles.cellContainer}>
<ImageBackground
// source={{ uri: product.images[0].src }}
style={styles.backgroundImage}
imageStyle={styles.imageStyle}
>
<View style={styles.textContainer}>
<NameText> {product.name} </NameText>
<PriceText> ${product.price} </PriceText>
</View>
</ImageBackground>
</View>
);
};
export default ProductListCellView;
const styles = {
cellContainer: {
borderBottomWidth: 0.5,
borderBottomColor: "grey",
width: "100%",
height: "50%",
borderWidth: 3,
backgroundColor: "lightblue"
},
backgroundImage: {
width: "100%",
height: "100%",
justifyContent: "center"
},
imageStyle: {
height: "140%",
width: "140%",
left: "-20%",
top: "-20%"
},
textContainer: {
backgroundColor: "black",
maxWidth: "50%",
padding: 5,
opacity: 0.75
}
};
const baseSize = 14;
const text = {
name: {
fontSize: baseSize + 8,
fontWeight: "bold",
color: "white"
},
price: { fontSize: baseSize + 4, color: "white" }
};
const NameText = props => <Text style={text.name}>{props.children}</Text>;
const PriceText = props => <Text style={text.price}>{props.children}</Text>;
It seems that whatever I set the height for cellContainer at, it renders the cell at that % of the screen (or of some container that seems based on screen height), and then the cell contents at the same % of the cell.
Also, the list isn't scrolling. I can see the next cell peeking out the bottom so the whole list is rendering, but it just bounces back when I try to scroll. I've tried wrapping various things in ScrollView with no luck. (I changed the cellContainer height to 15% in the screenshot below)
When I map the items manually (switching the return in the above code to use `, the height works fine, but the scrolling still doesn't work:
Has anybody else had this problem?

Rather than setting the height of cellContainer to a % value, set it to a static height, or using padding to automatically size each item.

Related

react native top tab bar navigator: indicator width to match text

I have three tabs in a top tab bar navigation with different width text. Is it possible to make the indicator width match the text? On a similar note, how can I make the tabs match the width of the text too without making it display weird. I've tried width auto but it doesn't stay center.
This is how it looks with auto width:
<Tab.Navigator
initialRouteName="Open"
tabBarOptions={{
style: {
backgroundColor: "white",
paddingTop: 20,
paddingHorizontal: 25
},
indicatorStyle: {
borderBottomColor: colorScheme.teal,
borderBottomWidth: 2,
width: '30%',
left:"9%"
},
tabStyle : {
justifyContent: "center",
width: tabBarWidth/3,
}
}}
>
<Tab.Screen
name="Open"
component={WriterRequestScreen}
initialParams={{ screen: 'Open' }}
options={{
tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> Open </Text>,
}}
/>
<Tab.Screen
name="In Progress"
component={WriterRequestScreen}
initialParams={{ screen: 'InProgress' }}
options={{
tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> In Progress </Text>}}
/>
<Tab.Screen
name="Completed"
component={WriterRequestScreen}
initialParams={{ screen: 'Completed' }}
options={{ tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> Completed </Text>}}
/>
</Tab.Navigator>
I also needed to make the indicator fit the text size, a dynamic width for the labels, and a scrollable top bar because of long labels. The result looks like this:
tab bar with dynamic indicator width
If you don't care about the indicator width fitting the labels, you can simply use screenOptions.tabBarScrollEnabled: true in combination with width: "auto" in screenOptions.tabBarIndicatorStyle.
Otherwise, you'll need to make your own tab bar component and pass it to the tabBar property of your <Tab.Navigator>. I used a ScrollView but if you have only a few tabs with short labels, a View would be more simple. Here is the Typescript code for this custom TabBar component:
import { MaterialTopTabBarProps } from "#react-navigation/material-top-tabs";
import { useEffect, useRef, useState } from "react";
import {
Animated,
Dimensions,
View,
TouchableOpacity,
StyleSheet,
ScrollView,
I18nManager,
LayoutChangeEvent,
} from "react-native";
const screenWidth = Dimensions.get("window").width;
const DISTANCE_BETWEEN_TABS = 20;
const TabBar = ({
state,
descriptors,
navigation,
position,
}: MaterialTopTabBarProps): JSX.Element => {
const [widths, setWidths] = useState<(number | undefined)[]>([]);
const scrollViewRef = useRef<ScrollView>(null);
const transform = [];
const inputRange = state.routes.map((_, i) => i);
// keep a ref to easily scroll the tab bar to the focused label
const outputRangeRef = useRef<number[]>([]);
const getTranslateX = (
position: Animated.AnimatedInterpolation,
routes: never[],
widths: number[]
) => {
const outputRange = routes.reduce((acc, _, i: number) => {
if (i === 0) return [DISTANCE_BETWEEN_TABS / 2 + widths[0] / 2];
return [
...acc,
acc[i - 1] + widths[i - 1] / 2 + widths[i] / 2 + DISTANCE_BETWEEN_TABS,
];
}, [] as number[]);
outputRangeRef.current = outputRange;
const translateX = position.interpolate({
inputRange,
outputRange,
extrapolate: "clamp",
});
return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
};
// compute translateX and scaleX because we cannot animate width directly
if (
state.routes.length > 1 &&
widths.length === state.routes.length &&
!widths.includes(undefined)
) {
const translateX = getTranslateX(
position,
state.routes as never[],
widths as number[]
);
transform.push({
translateX,
});
const outputRange = inputRange.map((_, i) => widths[i]) as number[];
transform.push({
scaleX:
state.routes.length > 1
? position.interpolate({
inputRange,
outputRange,
extrapolate: "clamp",
})
: outputRange[0],
});
}
// scrolls to the active tab label when a new tab is focused
useEffect(() => {
if (
state.routes.length > 1 &&
widths.length === state.routes.length &&
!widths.includes(undefined)
) {
if (state.index === 0) {
scrollViewRef.current?.scrollTo({
x: 0,
});
} else {
// keep the focused label at the center of the screen
scrollViewRef.current?.scrollTo({
x: (outputRangeRef.current[state.index] as number) - screenWidth / 2,
});
}
}
}, [state.index, state.routes.length, widths]);
// get the label widths on mount
const onLayout = (event: LayoutChangeEvent, index: number) => {
const { width } = event.nativeEvent.layout;
const newWidths = [...widths];
newWidths[index] = width - DISTANCE_BETWEEN_TABS;
setWidths(newWidths);
};
// basic labels as suggested by react navigation
const labels = state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label = route.name;
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: "tabPress",
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
// The `merge: true` option makes sure that the params inside the tab screen are preserved
// eslint-disable-next-line
// #ts-ignore
navigation.navigate({ name: route.name, merge: true });
}
};
const inputRange = state.routes.map((_, i) => i);
const opacity = position.interpolate({
inputRange,
outputRange: inputRange.map((i) => (i === index ? 1 : 0.5)),
});
return (
<TouchableOpacity
key={route.key}
accessibilityRole="button"
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
onPress={onPress}
style={styles.button}
>
<View
onLayout={(event) => onLayout(event, index)}
style={styles.buttonContainer}
>
<Animated.Text style={[styles.text, { opacity }]}>
{label}
</Animated.Text>
</View>
</TouchableOpacity>
);
});
return (
<View style={styles.contentContainer}>
<Animated.ScrollView
horizontal
ref={scrollViewRef}
showsHorizontalScrollIndicator={false}
style={styles.container}
>
{labels}
<Animated.View style={[styles.indicator, { transform }]} />
</Animated.ScrollView>
</View>
);
};
const styles = StyleSheet.create({
button: {
alignItems: "center",
justifyContent: "center",
},
buttonContainer: {
paddingHorizontal: DISTANCE_BETWEEN_TABS / 2,
},
container: {
backgroundColor: "black",
flexDirection: "row",
height: 34,
},
contentContainer: {
height: 34,
marginTop: 30,
},
indicator: {
backgroundColor: "white",
bottom: 0,
height: 3,
left: 0,
position: "absolute",
right: 0,
// this must be 1 for the scaleX animation to work properly
width: 1,
},
text: {
color: "white",
fontSize: 14,
textAlign: "center",
},
});
export default TabBar;
I managed to make it work with a mix of:
react navigation example
react-native-tab-view original indicator
jsindos answer
Please let me know if you find a more convenient solution.
You have to add width:auto to tabStyle to make tab width flexible.
Then inside each tabBarLabel <Text> component add style textAlign: "center" and width: YOUR_WIDTH .
YOUR_WIDTH can be different for each tab and can be your text.length * 10 (if you want to make it depended on your text length) or get screen width from Dimensions and divide it by any other number to make it equal widths in screen. Example:
const win = Dimensions.get('window');
...
bigTab: {
fontFamily: "Mulish-Bold",
fontSize: 11,
color: "#fff",
textAlign: "center",
width: win.width/2 - 40
},
smallTab: {
fontFamily: "Mulish-Bold",
fontSize: 11,
color: "#fff",
textAlign: "center",
width: win.width / 5 + 10
}
Remove width from indicatorStyle and use flex:1
indicatorStyle: { borderBottomColor: colorScheme.teal,
borderBottomWidth: 2,
flex:1,
left:"9%"
},
I've achieved this using some hacks around onLayout, please note I've made this with the assumptions of two tabs, and that the second tabs width is greater than the first. It probably will need tweaking for other use cases.
import React, { useEffect, useState } from 'react'
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs'
import { Animated, Text, TouchableOpacity, View } from 'react-native'
const Stack = createMaterialTopTabNavigator()
const DISTANCE_BETWEEN_TABS = 25
function MyTabBar ({ state, descriptors, navigation, position }) {
const [widths, setWidths] = useState([])
const [transform, setTransform] = useState([])
const inputRange = state.routes.map((_, i) => i)
useEffect(() => {
if (widths.length === 2) {
const [startingWidth, transitionWidth] = widths
const translateX = position.interpolate({
inputRange,
outputRange: [0, startingWidth + DISTANCE_BETWEEN_TABS + (transitionWidth - startingWidth) / 2]
})
const scaleX = position.interpolate({
inputRange,
outputRange: [1, transitionWidth / startingWidth]
})
setTransform([{ translateX }, { scaleX }])
}
}, [widths])
return (
<View style={{ flexDirection: 'row' }}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key]
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name
const isFocused = state.index === index
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true
})
if (!isFocused && !event.defaultPrevented) {
// The `merge: true` option makes sure that the params inside the tab screen are preserved
navigation.navigate({ name: route.name, merge: true })
}
}
const onLayout = event => {
const { width } = event.nativeEvent.layout
setWidths([...widths, width])
}
const opacity = position.interpolate({
inputRange,
outputRange: inputRange.map(i => (i === index ? 0.87 : 0.53))
})
return (
<TouchableOpacity
key={index}
accessibilityRole='button'
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
style={{ marginRight: DISTANCE_BETWEEN_TABS }}
>
<Animated.Text
onLayout={onLayout}
style={{
opacity,
color: '#000',
fontSize: 18,
fontFamily: 'OpenSans-Bold',
marginBottom: 15
}}
>
{label}
</Animated.Text>
</TouchableOpacity>
)
})}
<View style={{ backgroundColor: '#DDD', height: 2, position: 'absolute', bottom: 0, left: 0, right: 0 }} />
<Animated.View style={{ position: 'absolute', bottom: 0, left: 0, width: widths.length ? widths[0] : 0, backgroundColor: '#222', height: 2, transform }} />
</View>
)
}
export default () => {
return (
<>
<Stack.Navigator tabBar={props => <MyTabBar {...props} />} style={{ paddingHorizontal: 25 }}>
<Stack.Screen name='Orders' component={() => <Text>A</Text>} />
<Stack.Screen name='Reviews' component={() => <Text>B</Text>} />
</Stack.Navigator>
</>
)
}
Update:
If the menu names are static, it is probably a more robust solution to hard code the widths inside of widths, although this is a little more costly to maintain.
Resources:
https://reactnavigation.org/docs/material-top-tab-navigator/#tabbar
https://github.com/facebook/react-native/issues/13107
In the screenOptions, add following props for
tabBarScrollEnabled: true
tabBarItemStyle: {{width: "auto", minWidht: "100"}}
minWidth is just to keep the design consistent.
Please note, I am using react-navigation 6.x and Camille Hg answer was really helpful.
I had the same issue and I was finally able to make the indicator takes exactly the text size.
I don't know in which version this was possible .. but apparently you can add a custom indicator component (beside the ability to add a custom tabBar component)
when creating the TopTabNavigator it is important to add the properties as described in the code under
// assuming that you want to add paddingHorizontal: 10 for each item!
const TAB_BAR_ITEM_PADDING = 10;
const Tab = createMaterialTopTabNavigator();
function TopTabNavigator() {
return (
<Tab.Navigator
.....
... .
screenOptions={{
....
...
tabBarItemStyle: {
// these properties are important for this method to work !!
width: "auto",
marginHorizontal: 0, // this is to make sure that the spacing of the item comes only from the paddingHorizontal!.
paddingHorizontal: TAB_BAR_ITEM_PADDING, // the desired padding for the item .. stored in a constant to be passed in the custom Indicator
},
tabBarIndicator: props => {
return (
<CustomTabBarIndicator
// the default props
getTabWidth={props.getTabWidth}
jumpTo={props.jumpTo}
layout={props.layout}
navigationState={props.state}
position={props.position}
width={props.width}
style={{
left: TAB_BAR_ITEM_PADDING,
backgroundColor: Colors.primary,
}}
// this is an additional property we will need to make the indicator exactly
tabBarItemPadding={TAB_BAR_ITEM_PADDING}
/>
);
},
}}
>
<Tab.Screen .... />
<Tab.Screen ..... />
<Tab.Screen .... />
</Tab.Navigator>
);
}
now for the CustomTabBarIndIndicator component we simply go to the official github repository for react-native-tab-view and then go to TabBarIndicator.tsx and copy the component over in a file called CustomTabBarIndicator "just to be consistence with the example, but you can call it what ever you want", and don't forget to add the additional property to the Props type for tabBarItemPadding "if you are using typescript"
and now make this small change to the line that is highlighted in the image
change:
const outputRange = inputRange.map(getTabWidth);
to be:
const outputRange = inputRange.map(x => {
// this part is customized to get the indicator to be the same width like the label
// subtract the tabBarItemPadding from the tabWidth
// so that you indicator will be exactly the same size like the label text
return getTabWidth(x) - this.props.tabBarItemPadding * 2;
});
and that was it :)
P.S. I added the image because I didn't know how to exactly describe where to make the change
and if you don't want the typescript .. jsut remove all the types from the code and you are good to go :)

How to show 2 items in one pagination of react native snap carousel

How to dispaly 2 items for one pagination(first dot) and if we swipe then next 2 items should display with showing second dot active.
And if it is odd then last item should display my own component in react native snap carousel.
I would suggest that you go ahead and make the item you're rendering in the Carousel one that renders 2 things at once. The Carousel will paginate on whatever you pass to it, so if you're passing something with 2 items in it, it'll paginate on that, so for example:
<Carousel
layout="default"
data={arr}
renderItem={
({ item, index }) => (
<View style={styles.imageWrapper}>
<Image
style={styles.image}
source={item[0]}
resizeMode="cover"
accessibilityLabel="thumbnail"
/>
<Image
style={styles.image}
source={item[1]}
resizeMode="cover"
accessibilityLabel="thumbnail"
/>
</View>
)
}
lockScrollWhileSnapping={true} // Prevent the user from swiping again while the carousel is snapping to a position.
sliderWidth={screenWidth}
sliderHeight={screenWidth * 0.5}
itemWidth={screenWidth - 40}
activeSlideOffset={50}
enableSnap
onSnapToItem={onSnapToItem}
removeClippedSubviews={false}
firstItem={0}
contentContainerCustomStyle={styles.style}
/>
Create a function that split "entries" array into smaller arrays based on the size that you want
var slides = [];
const entriesSplitter = () => {
let size = 2; //Based on the size you want
while (entries.length > 0) {
slides.push(entries.splice(0, size));
}
};
then pass the slides array to <Carousel data={slides}/> then render each slide
in _renderItem
consider the following example:-
import React, { useState, useRef } from "react";
import { View,Text, Dimensions } from "react-native";
import Carousel, { Pagination } from "react-native-snap-carousel";
const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
const myCarousel = () => {
const [activeSlide, setActiveSlide] = useState(0);
const carousel = useRef();
const entries = [
{
title: "Adidas"
},
{
title: "Nike"
},
{
title: "Puma"
},
{
title: "Reebok"
}
];
var slides = [];
const entriesSplitter = () => {
let size = 2; //Based on the size you want
while (entries.length > 0) {
slides.push(entries.splice(0, size));
}
};
// render every single slide
const _renderItem = ({ item,index }) => {
return (
<View style={{ flexDirection: "row", flexWrap: "wrap" }}>
{item.map(item => {
return <Text key={index}>{item.title}</Text>;
})}
</View>
);
};
return (
<View>
{entriesSplitter()}
<Carousel
ref={carousel}
data={slides}
renderItem={_renderItem}
onSnapToItem={index => setActiveSlide(index)}
sliderWidth={screenWidth}
sliderHeight={screenHeight}
itemWidth={screenWidth}
/>
<Pagination
dotsLength={2} // also based on number of sildes you want
activeDotIndex={activeSlide}
containerStyle={{ backgroundColor: "red", borderWidth: 2 }}
dotStyle={{
width: 10,
height: 10,
borderRadius: 5,
marginHorizontal: 8,
backgroundColor: "black"
}}
inactiveDotStyle={{
backgroundColor: "pink"
}}
inactiveDotOpacity={0.4}
inactiveDotScale={0.6}
/>
</View>
);
};
export default myCarousel;
This is how I implemented to show 3 carousels. We can customize it in many ways
import React from "react";
import { View, Dimensions, StyleSheet, Image } from "react-native";
import Carousel from "react-native-snap-carousel";
const windowWidth = Dimensions.get("window").width;
export default function MyCarousel() {
const images = [
{ id: 1, image: require("../assets/home-slider-1.jpg") },
{ id: 2, image: require("../assets/home-slider-2.jpg") },
{ id: 3, image: require("../assets/home-slider-3.jpg") },
{ id: 4, image: require("../assets/home-slider-4.jpg") }
];
// const imagesUri = [
// { id: 1, image: { uri: 'https://i.imgur.com/gG5Egof.jpg' } },
// { id: 2, image: { uri: 'https://i.imgur.com/gG5Egof.jpg' } },
// { id: 3, image: { uri: 'https://i.imgur.com/gG5Egof.jpg' } },
// { id: 4, image: { uri: 'https://i.imgur.com/gG5Egof.jpg' } }
// ];
const _renderItem = ({ item }) => {
return (
<View style={styles.slide}>
<Image
source={item.image}
style={styles.image}
// resizeMode="center"
></Image>
</View>
);
};
return (
<View style={styles.wrapper}>
<Carousel
data={images}
renderItem={_renderItem}
sliderWidth={windowWidth}
itemWidth={windowWidth - 70}
enableMomentum={false}
lockScrollWhileSnapping
autoplay
useScrollView
loop
autoplayInterval={3000}
/>
</View>
);
}
const styles = StyleSheet.create({
wrapper: {
height: 150
},
slide: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff"
},
image: {
flex: 1,
height: "100%",
width: "100%",
alignItems: "center",
justifyContent: "center"
}
});

FlatList that renders components that contain FlatList

I am having trouble getting an 'nested' FlatList to scroll. I have disabled the parent FlatList scrolling by passing scrollEnabled = {false} but that had no effect. I think the parent FlatList is intercepting the scroll events and I have not been able to find anything online that could be a possible solution.
export const createStyles = () => {
return {
baseStyles: {
height: 70,
overflow: 'hidden',
width: 200,
paddingHorizontal: 5,
borderWidth: 2,
borderColor: StyleConst.colors.grey.lighter3,
borderRadius: 3,
},
};
};
export const createItemStyle = (index, len) => {
console.log(index, len);
if (index !== len - 1) {
return {
borderBottomColor: StyleConst.colors.grey.lighter2,
borderBottomWidth: 1,
width: 150,
}
}
}
export class Dropdown extends Component {
static propTypes = {
data: arrayOf(object),
handleChange: func,
valueExtractor: func.isRequired,
}
renderItem = (item) => {
return (
<TouchableWithoutFeedback onPress={() => this.props.handleChange(this.props.valueExtractor(item))}>
<View key = {item.id}>
<Text>
{item.item.value}
</Text>
</View>
</TouchableWithoutFeedback>
)
}
render() {
const styles = createStyles();
return (
<View style={styles.baseStyles}>
<FlatList
data={this.props.data}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
onScrollBeginDrag={() => console.log("start")}
/>
</View>
)
}
}

ListView doesn't scroll

I have a horizontal ListView (shown in the image with list items a,b,c) that will not scroll, or rather, it does scroll - but one out of 10 or so swipes seems to make it scroll. I have also used a FlatList - same result. The ListView is on a Interactable.View, however I got rid of the Interactable.View and it still didn't scroll. I've tested on a real ios device and a genymotion emulator and both had the same result. There are more items in the list than just a,b,c. There are 6 items in the list.
import { StyleSheet, View, Text, FlatList, ListView } from 'react-native'
import React, { Component } from 'react'
import MapView from 'react-native-maps'
import { connect } from 'react-redux'
import {
Button,
Container
} from 'native-base'
import { updateRegion } from './map.action'
import { OptimizedFlatList } from 'react-native-optimized-flatlist'
import Icon from 'react-native-vector-icons/FontAwesome'
import { toggleMenu } from '../search-page/searchPage.action'
import mapStyle from './style'
import Interactable from 'react-native-interactable'
import { setSelectedShop } from '../search-results/searchResults.action'
import { updateHeight } from '../search-results/searchResultsPresenter.action'
import { getSelectedProduct } from './markers.selector'
const mapStateToProps = (state) => ({
region: state.get('map').get('region'),
markers: state.get('searchResults').get('products'),
selectedProduct: getSelectedProduct(state),
height: state.get('searchResultsPresenter').get('height')
})
const mapDispatchToProps = (dispatch) => ({
onRegionChange: (region) => {
dispatch(updateRegion(region))
},
onToggleMenuClick: () => {
dispatch(toggleMenu())
},
setSelectedShop: id => {
dispatch(setSelectedShop(id))
},
updateHeight: height => {
dispatch(updateHeight(height))
}
})
class Map extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => { })
}
componentWillUnmount() {
this.unsubscribe()
}
componentWillReceiveProps(newProps) {
if (newProps.selectedProduct) {
let products = newProps.selectedProduct.products
this.setState({
dataSource: this.state.dataSource.cloneWithRows(products)
})
}
}
interactableView;
constructor(props) {
super(props)
this.state = { dataSource: new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }) }
}
render() {
console.log(this.props.height)
return (
<Container>
<MapView
style={styles.map}
region={this.props.region}
onRegionChangeComplete={this.props.onRegionChange}>
{
this.props.markers.map(marker => {
return (
<MapView.Marker
coordinate={marker.shop.coordinate}
title={marker.shop.name}
identifier={marker.shop.id.toString()}
onPress={e => {
console.log(e.nativeEvent)
this.interactableView.snapTo({ index: 0 })
this.props.setSelectedShop(marker.shop)
console.log(this.props.selectedProduct)
}}
/>
)
})
}
</MapView>
<Button
small
icon
style={mapStyle.toggleMenuButton}
onPress={() => this.props.onToggleMenuClick()}>
<Icon name="sliders" size={20} color="#FFFFFF" />
</Button>
<Interactable.View
style={{
flex: 1,
flexDirection: 'row',
zIndex: 20,
borderRadius: 10,
backgroundColor: '#222222',
padding: 30,
paddingTop: 50
}}
verticalOnly={true}
snapPoints={[{ y: this.props.height - 225 }, { y: this.props.height - 50 }]}
initialPosition={{ y: this.props.height - 50 }}
ref={view => this.interactableView = view}
onLayout={(event) => {
this.props.updateHeight(event.nativeEvent.layout.height)
}} >
<View style={{ flex: 1, flexDirection: 'row', height: 50 }}>
<Text
style={{
color: 'white',
position: 'absolute',
top: -40,
marginBottom: 20,
textAlign: 'center',
width: '100%'
}}>
{this.props.selectedProduct ? this.props.selectedProduct.shop.name : ''}
</Text>
<ListView
dataSource={this.state.dataSource}
horizontal={true}
style={{
height: 200
}}
renderRow={(rowData)=> {
console.log(rowData)
return (
<View style={{
backgroundColor: 'blue',
width: 100,
borderWidth: 1,
borderColor: 'black',
margin: 0
}}>
<Text style={{ color: 'white' }}>{rowData.name}</Text>
<View style={{
zIndex: 15,
width: '100%',
height: '100%',
backgroundColor: 'red'
}}>
</View>
</View>
)
}}
/>
</View>
</Interactable.View>
</Container>
)
}
}
Map.contextTypes = {
store: React.PropTypes.object
}
Map.propTypes = {
region: React.PropTypes.shape({
latitude: React.PropTypes.number,
longitude: React.PropTypes.number,
latitudeDelta: React.PropTypes.number,
longitudeDelta: React.PropTypes.number
}).isRequired,
height: React.PropTypes.number,
updateHeight: React.PropTypes.func,
setSelectedShop: React.PropTypes.func,
selectedProduct: React.PropTypes.string,
onRegionChange: React.PropTypes.func.isRequired,
onToggleMenuClick: React.PropTypes.func.isRequired,
markers: React.PropTypes.array
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Map)
const styles = StyleSheet.create({
map: {
...StyleSheet.absoluteFillObject,
zIndex: 3
}
})
How do I get the listview to scroll with every swipe, rather than about 1 out of 10 swipes?
Same thing happens when I use a ScrollView. So nothing can be swiped horizontally there. Even when removing the interactable.view
Making renderRow return a TouchableHighlight instead of a View caused the list to be draggable with every touch and swipe. And finally, unessential to the answer, I switched from a ListView to a FlatList because ListView is going to be deprecated in the future. I felt it good to state that as at this point in time it isn't clear from the official documentation that ListView is becoming deprecated.

react-native Navigator (navbar) Hiding View Under Navbar

I'm having a hard time figuring out which props I need to change in the initial view below the Navbar. Or is there a prop for the navbar I need to change? Basically, the navbar is hiding the top portion of the underlying view. I'm attaching a screen cap here:
Here is my code.
Navigator code:
var routeMapper = {
LeftButton: function(route, navigator, index, navState) {
if(index > 0) {
return (
<TouchableHighlight
underlayColor="transparent"
onPress={() => { if (index > 0) { navigator.pop() } }}>
<Text style={ styles.leftNavButtonText }>Back</Text>
</TouchableHighlight>)
}
else { return null }
},
RightButton: function(route, navigator, index, navState) {
return null;
},
Title: function(route, navigator, index, navState) {
return <Text style={ styles.navbarTitle }>EST4Life</Text>
}
};
module.exports = React.createClass({
renderScene: function(route, navigator) {
var Component = ROUTES[route.name]; // ROUTES['signin'] => Signin
// return the component with props to the current route and the navigator instance
return <Component route={route} navigator={navigator} />;
},
render: function() {
// return an instance of Navigator
return (
<Navigator
style={styles.container}
initialRoute={{name: 'signin'}}
renderScene={this.renderScene}
configureScene={() => { return Navigator.SceneConfigs.FloatFromRight; }}
navigationBar={<Navigator.NavigationBar
routeMapper={routeMapper}
style={styles.navBarStyle}
/>}
/>
)
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
navBarStyle: {
backgroundColor: 'gray',
flex: 1,
borderWidth: 1
},
navbarTitle: {
color: 'white',
},
rightNavButtonText: {
color: 'white',
},
leftNavButtonText: {
color: 'white',
},
})
signin.js
module.exports = React.createClass({
render: function() {
if (this.state.user == '') { // this.state.user = '' when signin is initiated
return <View style={styles.container}><Text>Loading...</Text></View>
} else if (this.state.user == null){ // this.state.user == null when user is not logged in
return (
<View style={styles.container}>
<Text>Sign In</Text>
<Text style={styles.label}>Username:</Text>
<TextInput
style={styles.input}
value={this.state.username}
onChangeText={(text) => this.setState({username: text})}
/>
<Text style={styles.label}>Password:</Text>
<TextInput
secureTextEntry={true}
style={styles.input}
value={this.state.password}
onChangeText={(text) => this.setState({password: text})}
/>
<Text style={styles.label}>{this.state.errorMessage}</Text>
<Button text={'Sign In'} onPress={this.onLoginPress} />
<Button text={'I need an account..'} onPress={this.onSignupPress} />
</View>
); // onPress={this.onPress} - pass in the onPress method below to TouchableHighlight
} else { // clear view when user is logged in
return <View><Text></Text></View>
}
}, // end of render
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 4,
borderColor: 'green'
},
input: {
padding: 5, // space between text and inside of box
height: 40,
borderColor: 'gray',
borderWidth: 1,
borderRadius: 5,
margin: 5,
width: 200,
alignSelf: 'center',
},
label: {
fontSize: 18
}
});
Thanks.
Update: I figured out how to extract the heights involved with the navbar.
Code for use where the component is created:
var NavBarHeight = navigator.props.navigationBar.props.navigationStyles.General.NavBarHeight
var StatusBarHeight = navigator.props.navigationBar.props.navigationStyles.General.StatusBarHeight
var TotalNavHeight = navigator.props.navigationBar.props.navigationStyles.General.TotalNavHeight
Code to use in any scene thereafter:
var NavBarHeight = this.props.navigator.props.navigationBar.props.navigationStyles.General.NavBarHeight
var StatusBarHeight = this.props.navigator.props.navigationBar.props.navigationStyles.General.StatusBarHeight
var TotalNavHeight = this.props.navigator.props.navigationBar.props.navigationStyles.General.TotalNavHeight
Give your signin.js container some marginTop. That will get it done.
Can you try using sceneStyle for navigator? This applies to every scene being rendered using this navigator. So you can try something like { marginTop: 55 } and adjust from there based on the height of your navigation bar.
https://facebook.github.io/react-native/docs/navigator.html#scenestyle