React Native Gesture Handler + Reanimated Flat List Scroll - reactjs

I'm having a problem with react-native-gesture handler animation in a FlatList.
when i try to scroll on the flatList the behavior of PanGesture not trigger scroll event.
Reanimated Version - ~2.5
Flatlist
<FlatList
data={test}
ref={flatListRef}
ItemSeparatorComponent={Divider}
style={{
marginTop: 18,
}}
scrollEnabled
renderItem={({ item }) => {
return (
<ActionItem
simultaneousHandlers={flatListRef}
actions={[
{
type: 'UPDATE',
handlePress: () => {
console.log(item.id);
},
},
{
type: 'UPDATE',
handlePress: () => {
console.log(item.id);
},
},
]}
>
<View style={styles.container}>
<Text style={{}}>{item.name}</Text>
</View>
</ActionItem>
);
}}
/>
ActionItem
import { useTheme } from '#hooks';
import React from 'react';
import {
StyleSheet,
View,
Text,
Dimensions,
TouchableOpacity,
} from 'react-native';
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
withSpring,
} from 'react-native-reanimated';
import { IActionItem } from './types';
import Edit from '#assets/svgs/icons/edit.svg';
const threshold = (-Dimensions.get('window').width - 18) * 0.05;
function ActionItem<T>({
children,
actions,
simultaneousHandlers,
}: IActionItem<T>) {
const [actionsWidth, setActionsWidth] = React.useState<number>(0);
const translateX = useSharedValue(0);
const theme = useTheme();
const gestures = useAnimatedGestureHandler<PanGestureHandlerGestureEvent>({
onStart: () => {
translateX.value = withTiming(0);
},
onActive: (e) => {
if (e.translationX < 0) {
translateX.value = withSpring(e.translationX);
}
},
onEnd: (e) => {
const shouldHideActions = e.translationX > threshold;
if (shouldHideActions) {
translateX.value = withTiming(0);
} else {
translateX.value = withSpring(-actionsWidth);
}
},
});
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{
translateX: translateX.value,
},
],
};
});
return (
<Animated.View style={styles.container}>
<Animated.View
style={styles.actionsContainer}
onLayout={(e) => {
setActionsWidth(e.nativeEvent.layout.width);
}}
>
{actions?.map((action, index) => {
return (
<TouchableOpacity
key={index}
onPress={action.handlePress}
style={[
{
marginRight: index !== actions.length - 1 ? 20 : 0,
marginLeft: index === 0 ? 20 : 0,
},
]}
>
{action.type === 'UPDATE' && <Edit />}
</TouchableOpacity>
);
})}
</Animated.View>
<PanGestureHandler
simultaneousHandlers={simultaneousHandlers}
onGestureEvent={gestures}
>
<Animated.View style={[animatedStyles, styles.action]}>
{children}
</Animated.View>
</PanGestureHandler>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
elevation: 10,
},
actionsContainer: {
position: 'absolute',
flexDirection: 'row',
alignItems: 'center',
height: '100%',
flex: 1,
right: 0,
},
action: {
backgroundColor: 'red',
},
});
export default ActionItem;
i'm using the flat list from React-Native-Gesture-handler and i'm passing the reference of flatlist to scroll work but doesn't work, anyone have some ideia why ?

Related

How to use a Pan responder to scroll a scrollView (React)

I have a pan responder overlaying the whole screen and a scrollview underneath it.
I would like to call the scrollTo() method and using Pan responders X/Y travel positions to scroll.
I am using this Pan Responder example code to create a Y value that can increment or decrement as you swipe up or down on the screen.
https://reactnative.dev/docs/panresponder
I don't know how to get myScroll view to listen to this Y value change.
Any help is appreciated. thanks
// You should be able to run this by copying & pasting
import { createRef, useRef } from "react";
import { Animated, PanResponder, StyleSheet, Text, View } from "react-native";
// some refs...
const scrollRef = createRef();
const buttonRef = createRef();
// panResponder Hook
const useScreenPanResponder = () => {
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.setOffset({
y: pan.y._value,
});
},
onPanResponderMove: Animated.event([null, { dy: pan.y }]),
onPanResponderRelease: () => {
pan.flattenOffset();
},
})
).current;
return { pan, panResponder };
};
// custom button
const ButtonWrapper = ({ children }) => {
return (
<View
onTouchStart={() => {
buttonRef.current = Date.now();
}}
onTouchEnd={() => {
if (Date.now() - buttonRef.current < 500) {
console.log("Actual Press");
} else {
console.log("Not a Press");
}
}}
>
{children}
</View>
);
};
// long list of text
const Content = () => {
const data = Array.from(Array(100));
return (
<View style={{ backgroundColor: "orange", flexDirection: "column" }}>
{data.map((i) => (
<Text style={{ fontSize: 17, color: "black" }}>Some Content</Text>
))}
</View>
);
};
export default function App() {
const { pan, panResponder } = useScreenPanResponder();
console.log("pan!", pan);
console.log("scrollRef", scrollRef);
const scrollToPosition = () => {
// scrollRef.current.scrollTo({ y: pan.y });
};
return (
<View style={styles.container}>
{/* Container taking up the whole screen, lets us swipe to change pan responder y pos */}
<Animated.View
style={{
position: "absolute",
width: "100%",
height: "100%",
backgroundColor: "rgba(0,255,0,.5)",
}}
{...panResponder.panHandlers}
/>
{/* A animated container that reacts to pan Responder Y pos */}
<Animated.View
style={{
transform: [{ translateY: pan.y }],
}}
>
<ButtonWrapper>
<View style={styles.box} {...panResponder.panHandlers} />
</ButtonWrapper>
</Animated.View>
{/* Here we need the scrollView to be controlled by the Pan Y pos */}
<Animated.ScrollView ref={scrollRef}>
<Content />
</Animated.ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
titleText: {
fontSize: 14,
lineHeight: 24,
fontWeight: "bold",
},
box: {
height: 150,
width: 150,
backgroundColor: "blue",
borderRadius: 5,
},
});

How to display a value back in parent component in react native?

I have a page which shows list of coins.
From there I navigated to another component and passing the coin name as param.
From the second page, I have filtered all the orders based on the coin name received in params.Here, I calculated the average value. How to pass this back to parent page so that I can see the average value of orders beside each coin?
//screen1 code:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
TouchableWithoutFeedback,
} from "react-native";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
export default function CoinCreate(props) {
const [coinText, setCoinText] = useState("");
const [coins, setCoins] = useState([]);
const coinRef = firebase.firestore().collection("coins");
const userID = props.extraData.id;
useEffect(() => {
coinRef
.where("authorID", "==", userID)
.orderBy("createdAt", "desc")
.onSnapshot(
(querySnapshot) => {
const newCoins = [];
querySnapshot.forEach((doc) => {
const coin = doc.data();
coin.id = doc.id;
newCoins.push(coin);
});
setCoins(newCoins);
},
(error) => {
console.log(error);
}
);
}, []);
const onAddButtonPress = () => {
if (coinText && coinText.length > 0) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
text: coinText,
authorID: userID,
createdAt: timestamp,
};
coinRef
.add(data)
.then((_doc) => {
setCoinText("");
Keyboard.dismiss();
})
.catch((error) => {
alert(error);
});
}
};
const renderCoin = ({ item, index }) => {
return (
<View style={styles1.rowFront}>
<TouchableWithoutFeedback
onPress={() =>
props.navigation.navigate("Orders", {
coin: item.text,
userID: userID,
})
}
>
<Text>
{index}. {item.text}
</Text>
</TouchableWithoutFeedback>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new coin"
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setCoinText(text)}
value={coinText}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
{coins && (
<SwipeListView
data={coins}
keyExtractor={(item) => item.id}
renderItem={renderCoin}
removeClippedSubviews={true}
/>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
//screen2 code:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
} from "react-native";
import { Avatar, Button, Card, Title, Paragraph } from "react-native-paper";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { Icon } from "react-native-elements";
import { createIconSetFromFontello } from "#expo/vector-icons";
export default function OrderList(props) {
const LeftContent = (props) => <Avatar.Icon {...props} icon="folder" />;
const [orderText, setOrderText] = useState("");
const [orders, setOrders] = useState([]);
const orderRef = firebase.firestore().collection("orders");
const userID = props.route.params.userID;
const coin = props.route.params.coin;
//averageValue = (totalCost / totalCount).toString();
const [averageValue, setAverageValue] = useState("");
useEffect(() => {
orderRef
.where("authorID", "==", userID)
.where("name", "==", coin)
.orderBy("createdAt")
.onSnapshot(
(querySnapshot) => {
const newOrders = [];
querySnapshot.forEach((doc) => {
const order = doc.data();
order.id = doc.id;
newOrders.push(order);
});
setOrders(newOrders);
},
(error) => {
console.log(error);
}
);
}, []);
useEffect(() => {
//calculate and set anything like totalCost, averageValue, etc here
console.log("---came to orders effect---");
//console.log(orders);
let totalCost = 0;
let totalCount = 0;
orders.forEach((item, index) => {
console.log(item);
console.log(index);
totalCost += parseFloat(item.amount);
totalCount += parseFloat(item.count);
});
setAverageValue((totalCost / totalCount).toString());
}, [orders]);
/*
useEffect(() => {
let avg = (parseFloat(totalCost) / parseFloat(totalCount)).toString();
console.log("Avg:" + avg);
setAverageValue(avg);
}, [totalCount, totalCost]);
*/
const onAddButtonPress = () => {
props.navigation.navigate("CreateOrder", {
coin: coin,
userID: userID,
orderRef,
});
};
const renderOrder = ({ item, index }) => {
//console.log("----------------------");
//console.log(item.createdAt.toDate().toString());
//console.log("----------------------");
//setTotalCost(parseFloat(totalCost) + parseFloat(item.price));
//setTotalCount(parseFloat(totalCount) + parseFloat(item.count));
//totalCost = parseFloat(totalCost) + parseFloat(item.price);
//totalCount = parseFloat(totalCount) + parseFloat(item.count);
//console.log(totalCost);
//console.log(totalCount);
return (
<View style={styles1.rowFront}>
<Text>
{index}. {item.price} {item.amount} {item.count}
{"\n" + item.createdAt.toDate().toString()}
</Text>
<Icon name={"flight-takeoff"} />
</View>
);
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Click here to create new order..</Text>
</TouchableOpacity>
{orders.length === 0 && (
<Text>Please order some coins in currency: {coin}</Text>
)}
{orders && orders.length > 0 && (
<>
<Text>Average Value: {averageValue}</Text>
<SwipeListView
data={orders}
keyExtractor={(item) => item.id}
renderItem={renderOrder}
removeClippedSubviews={true}
/>
</>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
To simplify the process, I created a normal function(not react component) with orderListData(coin,userID) in a separate file and imported that and calling that function from the screen 1, but getting error as
TypeError: (0, _OrderListData.orderListData) is not a function. (In '(0, _OrderListData.orderListData)("ada", "nMOpBupcptZF8YlxiJYFX7vPMtC2")', '(0, _OrderListData.orderListData)' is undefined)
My code:
OrderListData.js
import { firebase } from "../../firebase/config";
export default function orderListData(coin, userID) {
let orders = [];
const orderRef = firebase.firestore().collection("orders");
//const userID = props.route.params.userID;
//const coin = props.route.params.coin;
let totalCost = 0;
let totalCount = 0;
let averagePrice = 0;
orderRef
.where("authorID", "==", userID)
.where("name", "==", coin)
.orderBy("createdAt")
.onSnapshot(
(querySnapshot) => {
querySnapshot.forEach((doc) => {
const order = doc.data();
order.id = doc.id;
orders.push(order);
});
},
(error) => {
console.log(error);
}
);
orders.forEach((item, index) => {
totalCost += parseFloat(item.amount);
totalCount += parseFloat(item.count);
});
averagePrice = totalCost / totalCount;
return {
averagePrice,
totalCost,
totalCount,
};
}
And the modified screen 1:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
TouchableWithoutFeedback,
} from "react-native";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { orderListData } from "./OrderListData";
export default function CoinCreate(props) {
console.log("testing");
console.log(orderListData("ada", "nMOpBupcptZF8YlxiJYFX7vPMtC2"));
const [coinText, setCoinText] = useState("");
const [coins, setCoins] = useState([]);
const coinRef = firebase.firestore().collection("coins");
const userID = props.extraData.id;
useEffect(() => {
coinRef
.where("authorID", "==", userID)
.orderBy("createdAt", "desc")
.onSnapshot(
(querySnapshot) => {
const newCoins = [];
querySnapshot.forEach((doc) => {
const coin = doc.data();
coin.id = doc.id;
newCoins.push(coin);
});
setCoins(newCoins);
},
(error) => {
console.log(error);
}
);
}, []);
const onAddButtonPress = () => {
if (coinText && coinText.length > 0) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
text: coinText,
authorID: userID,
createdAt: timestamp,
};
coinRef
.add(data)
.then((_doc) => {
setCoinText("");
Keyboard.dismiss();
})
.catch((error) => {
alert(error);
});
}
};
const renderCoin = ({ item, index }) => {
let { averagePrice, totalCost, totalCount } = orderListData(
item.text,
userID
);
return (
<View style={styles1.rowFront}>
<TouchableWithoutFeedback
onPress={() =>
props.navigation.navigate("Orders", {
coin: item.text,
userID: userID,
})
}
>
<Text>
{index}. {item.text} {averagePrice}
</Text>
</TouchableWithoutFeedback>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new coin"
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setCoinText(text)}
value={coinText}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
{coins && (
<SwipeListView
data={coins}
keyExtractor={(item) => item.id}
renderItem={renderCoin}
removeClippedSubviews={true}
/>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
Any suggestion on this please.
There are different ways to solve this problem,
Your requirement is to update one screen based on another screen, so you can think of using something state management for this but in your case you have the trigger of going back so use the functions of the navigation library to do that.
So I'll put a working example here using a simple scenario of sending an array
Let say that you have a home screen, from here we go to a CreateOrder screen and it will send data do this screen via navigation.navigate (navigate function will navigate to an existing screen or push a new screen so here you will be sent back).
function HomeScreen({ navigation, route }) {
React.useEffect(() => {
if (route.params?.orders) {
}
}, [route.params?.orders]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Create post"
onPress={() => navigation.navigate('CreateOrder')}
/>
<Text style={{ margin: 10 }}>Post: {route.params?.orders?.length}</Text>
</View>
);
}
You can use the useEffect to update if the param is updated and use the route prop.
The second screen would override the back button and also have a button to navigate back. Here we send the orders array via navigation.navigate
function CreateOrdersScreen({ navigation, route }) {
const [orders, setOrders] = React.useState('');
React.useLayoutEffect(() => {
navigation.setOptions({
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
navigation.navigate('Home', { orders });
}}
/>
),
});
}, [navigation, orders]);
return (
<>
<Text>{orders.length}</Text>
<Button
title="Add"
onPress={() => {
setOrders([...orders, 1]);
}}
/>
<Button
title="Done"
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { orders },
merge: true,
});
}}
/>
</>
);
}
You can see a running example here
https://snack.expo.io/#guruparan/5b84d0
References
https://reactnavigation.org/docs/params/#passing-params-to-a-previous-screen
https://reactnavigation.org/docs/header-buttons/#overriding-the-back-button

Flatlist with calling API on scroll does not work properly

I am using Function component in react native.
In that I am use Flatlist for data view. on onEndReached function of FlatList i am calling API for fetching data.
My Problem is
on scroll API call twice some times
on the Finish of all data. ActivityIndicator is showing in ListFooterComponent function.
Can you please help me.
Thanks in Advance.
Here is my code:
import React, { useState, useEffect } from "react";
import {
View,
StyleSheet,
ActivityIndicator,
Image,
FlatList,
TouchableHighlight,
} from "react-native";
import MainStyle from "../constants/Style";
import Layout from "../constants/Layout";
import Colors from "../constants/Colors";
import Services from "../Services";
const LIMIT_PER_PAGE = 10;
let fetching = false;
let isSubscribed = true;
let isListEnd = false;
let pageNo = 1;
var sortby = "latest";
let width = Layout.window.width / 2 - 16;
export default function PostListScreen(props) {
const [posts, setPosts] = useState([]);
fetchAllLatestPost = function () {
console.log("fetchAllLatestPost", fetching, isListEnd);
if (!fetching && !isListEnd) {
fetching = true;
Services.getAllPost(sortby, pageNo, LIMIT_PER_PAGE)
.then(function (res) {
if (!!res) {
console.log("res.length", res.length)
if (!!res.length && isSubscribed) {
pageNo++;
setPosts((posts) => {
posts = [...posts, ...res];
return posts;
});
} else {
isListEnd = true;
}
fetching = false;
}
});
}
};
useEffect(() => {
fetching = false;
isListEnd = false;
pageNo = 1;
isSubscribed = true;
fetchAllLatestPost();
return () => (isSubscribed = false);
}, []);
let readyImage = function (post) {
if (!!post.urls && !!post.urls.regular) {
return typeof post.urls.regular === "string"
? { uri: post.urls.regular }
: post.urls.regular;
} else {
return require("../assets/images/logo_white.png");
}
};
let renderItem = function (post, index) {
return (
<TouchableHighlight onPress={() => onPressPost(post)} key={index}>
<View style={styles.item}>
<Image
source={readyImage(post)}
style={{
flex: 1,
width: null,
height: null,
resizeMode: "cover",
borderRadius: 4,
}}
/>
</View>
</TouchableHighlight>
);
};
let renderFooter = function () {
return (
<View style={styles.footer}>
{console.log("fetching in footer ", fetching)}
{!!fetching ? (
<ActivityIndicator color="white" style={{ margin: 15 }} />
) : null}
</View>
);
};
return (
<View style={MainStyle.wrapper}>
<FlatList
numColumns={2}
keyExtractor={(item, index) => index.toString()}
data={posts}
onEndReached={() => fetchAllLatestPost()}
onEndReachedThreshold={0.5}
renderItem={({ item, index }) => <View>{renderItem(item, index)}</View>}
getItemLayout={(data, index) => ({
length: width,
offset: (width/2) * index,
index,
})}
ListFooterComponent={renderFooter()}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
flexWrap: "wrap",
},
item: {
height: width,
width: width,
margin: 8,
},
footer: {
padding: 10,
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
},
});
This is how I handled it
<FlatList
data={this.state.posts}
onRefresh={() => {
if (!this.props.posts.fetching) {
this.setState({
page: 1,
checkData: true,
posts: [],
});
this.props.postsRequest({page: 1});
}
}}
// onEndReached={}
keyExtractor={item => item.uid}
onMomentumScrollEnd={() => {
if (!this.props.posts.fetching) {
this.props.postsRequest({page: this.state.page + 1});
this.setState({page: this.state.page + 1, checkData: true});
}
}}
onEndReachedThreshold={0}
refreshing={this.props.posts.fetching}
showsVerticalScrollIndicator={false}
contentContainerStyle={{paddingBottom: 50}}
style={{padding: 10, marginVertical: 10}}
renderItem={item => (
<View style={{marginVertical: 5}}>
<Posts post={item.item} navigation={this.props.navigation} />
</View>
)}
/>

Set initial region after getting current location isn't working

I am using functional component with Hooks and I am getting user current location and assign it to MapView initialRegion but component doesn't rerender.
I tried to use reference ref with MapView component to call animateToRegion() but it's not working.
Is there is any thing that I miss?
here is my code sample:
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, Image, Platform, ScrollView, ActivityIndicator } from 'react-native';
import { DateMenu, Dropdown, DropdownTextInput, InputField} from '../../../GlobalReusableComponents/TextFields';
import { Space } from '../../../GlobalReusableComponents/Separators';
import { Button } from 'react-native-elements';
import axios from 'axios';
import MapView, { Marker } from 'react-native-maps';
import useReverseGeocoding from '../../../context/useReverseGeocoding';
import useCurrentLocation from '../../../context/useCurrentLocation';
import { getLocationAsync } from '../../../util/getCurrentLocation';
import { textStyles, buttonStyles } from '../../../globalStyles/styles';
import EStyleSheet from 'react-native-extended-stylesheet';
const latitudeDelta = 0.025
const longitudeDelta = 0.025
const JobLocationScreen = (props) => {
const [region, setRegion] = useState({
latitude: 38.907192,
longitude: -30.036871,
latitudeDelta,
longitudeDelta
});
const [latitude, setLatitude] = useState(null);
const [longitude, setLongitude] = useState(null);
const [reverseGeocodingApi, reverseGeocodingdata, isReverseGeacodingDone, reverseGeocodingErrorMessage] = useReverseGeocoding();
let mapRef = useRef(null);
useEffect(() => {
getYourCurrentLocation();
},
[])
useEffect(() => {
animateToRegion();
},
[region])
const onMapReady = () => {
if(!isMapReady) {
setIsMapReady(true);
}
};
const getYourCurrentLocation = async () => {
const { location } = await getLocationAsync();
console.log(location);
setRegion(region);
}
const onRegionChangeComplete = (selectedRegion) => {
setRegion(selectedRegion);
reverseGeocodingApi(selectedRegion);
}
const animateToRegion = () => {
mapRef.animateToRegion(region, 1000);
}
const onNextButtonPress = () => {
props.navigation.state.params.setSelectedValue(jobTags);
props.navigation.pop();
}
const _renderMapWithFixedMarker = () => {
return (
<View style={{flex: 1}}>
<MapView
ref={ref => {
mapRef = ref
}}
onMapReady={onMapReady}
style={styles.map}
initialRegion={region}
onRegionChangeComplete={(selectedRegion) => onRegionChangeComplete(selectedRegion)}
/>
<View style={styles.pinBadge}>
<Text
style={{color: EStyleSheet.value('$primaryDarkGray')}}
>
Move to choose Location
</Text>
</View>
<View style={styles.markerFixed}>
<Image style={styles.marker} source={require('../../../assets/pin.png')} />
</View>
</View>
);
}
return (
<View style={styles.container}>
<View
pointerEvents='none'
style={styles.inputFieldContainer}
>
<InputField
maxLength={35}
placeholder='Selected Address'
value={isReverseGeacodingDone? reverseGeocodingdata.results[0].formatted_address : 'Loading ...'}
/>
</View>
{_renderMapWithFixedMarker()}
<View style={styles.bodyContainer}>
<View style={styles.buttonContainer}>
<Button
title="Confirm Your Location"
buttonStyle={buttonStyles.button}
onPress={() => onNextButtonPress()}
/>
</View>
</View>
</View>
);
}
JobLocationScreen.navigationOptions = ({navigation}) => {
return {
title: 'Select your Location'
};
};
export default JobLocationScreen;
const styles = EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor'
},
inputFieldContainer: {
backgroundColor: '#f8f9f9',
paddingVertical: 20,
paddingHorizontal: 20
},
map: {
flex: 1
},
pinBadge: {
position: 'absolute',
paddingVertical: 10,
paddingHorizontal: 15,
top: '38%',
alignSelf: 'center',
borderRadius: 25,
backgroundColor: '#ffffff',
shadowColor: '#acaeb4',
shadowOffset: {
width: 0,
height: 3,
},
shadowOpacity: 0.5,
shadowRadius: 5,
elevation: 5
},
markerFixed: {
left: '50%',
marginLeft: -10,
marginTop: -6,
position: 'absolute',
top: '50%'
},
marker: {
width: 20,
height: 41
},
bodyContainer: {
marginHorizontal: 20
},
buttonContainer: {
position: 'absolute',
bottom: 20,
width: '100%'
}
})
You need to access the ref using refname.current to get access to the value.
<MapView ref={mapRef}
and then when want to access it, use .current:
const animateToRegion = () => {
mapRef.current.animateToRegion(region, 1000);
}
See the docs
Conceptual Demo
for react-native-maps-super-cluster
<MapView ref={mapRef}
const animateToRegion = () => {
mapRef.current.getMapRef().animateToRegion(region, 1000);
}

React native how to animate view opening up->down

Consider the following component where the user selects an option from a list:
import React, { Component } from "react";
import PropTypes from "prop-types";
import {
View,
Text,
StyleSheet,
Platform,
FlatList,
TouchableNativeFeedback,
TouchableOpacity,
PLatform
} from "react-native";
import Icon from "react-native-vector-icons/Ionicons";
import { colors, metrics } from "../../themes";
const Touchable =
Platform.OS === "android" ? TouchableNativeFeedback : TouchableOpacity;
class MenuSelector extends Component<{
onSelect: () => any,
config: object,
selected: string
}> {
state = {
listOpened: false
};
handlePress = id => {
this.props.onPress(id);
};
handleSelect = id => {
if (this.props.onSelect) this.props.onSelect(id);
this.setState({
listOpened: false
});
};
handleMenu = () => {
this.setState({
listOpened: !this.state.listOpened
});
};
render = () => {
let title = "";
if (this.props.config) {
title = this.props.config[0].title;
if (this.props.selected) {
let found = this.props.config.find(item => {
return item.id === this.props.selected;
});
if (found) title = found.title;
}
}
let top = (
<View style={styles.header}>
<Text style={styles.text}>{title}</Text>
<Touchable>
<Text style={styles.text}>
<Icon
name={"ios-menu"}
size={20}
onPress={this.handleMenu}
/>
</Text>
</Touchable>
</View>
);
let list = null;
if (this.state.listOpened === true) {
list = (
<FlatList
data={this.props.config}
renderItem={({ item }) => (
<Touchable onPress={this.handleSelect}>
<Text style={[styles.text, styles.listItem]}>{item.title}</Text>
</Touchable>
)}
/>
);
}
return (
<View style={styles.container}>
{top}
{list}
</View>
);
};
}
export default MenuSelector;
const styles = StyleSheet.create({
container: {
flex: -1,
flexDirection: "column"
},
header: {
flex: -1,
flexDirection: "row",
justifyContent: "space-between",
padding: 10,
backgroundColor: "blue"
},
text: {
fontSize: 16,
color: "white",
textAlign: "center",
fontWeight: "bold"
},
listItem: {
padding: 10,
backgroundColor: "blue"
}
});
The component is used in the following context:
let config = [
{
title="Option 1",
id="option1"
},
{
title="Option 2",
id="option2"
},
{
title="Option 3",
id="option3"
},
{
title="Option 4",
id="option4"
},
];
return (
<View style={styles.container}>
<MenuSelector
config={config.options}
selected={this.state.mode}
onPress={this.handleButtonListPress}
/>
<FlatList
data={this.props.data}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<Text style={styles.text}>{item.name}</Text>
)}
/>
</View>
);
As it is, the <MenuSelector> component appears "at once" on screen. I need to add an sliding effect to <MenuSelector>, "pushing down" the data FlatList when appearing on screen...
On closing, same behaviour, but animating from down to up.
How can I add such animation behaviour to my MenuSelector component ?

Resources