How can I pass a reference of a mapview to another component? - react-native

I have a map view, and in another componenet, I would like to be able to have a function that animates that mapview. That function would need a reference to my map view. How can I access my maps reference inside another component?
I have a snack expo that reproduces my problem exactly here as well as some code below. Please note which files are classes and which are functions. The files are too big in my projects, I do not want to change them
EDIT: Could I use some sort of state library such as Context to store the ref?
export default function App() {
return (
<View style={styles.container}>
<Map/>
<AnimateMapButton/>
</View>
);
}
I cant access this._map for obvious reasons. How can I access this?
export default class AnimateMapButton extends React.Component {
goToLocation = () => {
this._map.animateToRegion({
latitude: 103.1561,
longitude: -47.1651,
latitudeDelta: 0.0025,
longitudeDelta: 0.0025,
})
}
render() {
return (
<View style={{height: 75, width: 200, backgroundColor: 'red', position: 'absolute', top: 100}}>
<TouchableOpacity onPress={() => this.goToLocation()}>
<Text style={{fontSize: 20, }}>Click to animate the map</Text>
</TouchableOpacity>
</View>
);
}
}
export default class Map extends React.Component {
render(){
return (
<View style={styles.container}>
<MapView
style={styles.map}
ref={map => this._map = map}
/>
</View>
);
}
}

I suggest you use React.forwardRef in such scenarios.
map.js
export default React.forwardRef((props, ref) => {
return (
<View style={styles.container}>
<MapView
ref={ref}
style={styles.map}
/>
</View>
);
})
animatemapbutton.js
export default class AnimateMapButton extends React.Component {
render() {
return (
<View style={{height: 75, width: 200, backgroundColor: 'red', position: 'absolute', top: 100}}>
<TouchableOpacity onPress={() => this.props.onGotoLocation()}>
<Text style={{fontSize: 20, }}>Click to animate the map</Text>
</TouchableOpacity>
</View>
);
}
}
App.js
export default function App() {
const _mapRef = React.createRef();
goToLocation = () => {
_mapRef.current.animateToRegion({
latitude: 103.1561,
longitude: -47.1651,
latitudeDelta: 0.0025,
longitudeDelta: 0.0025,
})
}
return (
<View style={styles.container}>
<Map ref={_mapRef}/>
<AnimateMapButton onGotoLocation={goToLocation} />
</View>
);
}

You have to use a combination of reference and event callback method in the root component like this -
App.js
export default function App() {
const mapRef = React.useRef();
const goToLocation = () => {
mapRef.current.animateToRegion({
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
})
}
return (
<View style={styles.container}>
<Map mapRef={mapRef} />
<AnimateMapButton goToLocation={goToLocation} />
</View>
);
}
animatemapbutton.js
export default function Animatemapbutton({ goToLocation }) {
return (
<TouchableOpacity
style={{ height: 75, width: 200, backgroundColor: 'red', position: 'absolute', top: 100 }}
onPress={() => goToLocation()}
>
<Text style={{fontSize: 20, }}>Click to animate the map</Text>
</TouchableOpacity>
);
}
map.js
export default function Map({ mapRef }) {
return (
<View style={styles.container}>
<MapView
ref={mapRef}
style={styles.map}
/>
</View>
);
}

Related

react native navigation nested screen

i have screen with 2 tabs
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Main" component={Main}/>
<Tab.Screen name="Chat" component={Chat}/>
</Tab.Navigator>
</NavigationContainer>
Screen Chat very simple and it works :
const Chat = () => {
return (
<View>
<Text>Chat</Text>
</View>
);
};
export default Chat;
But in first screen Main i have nested screen Map
const Main = () => {
return (
<Map/>
);
};
export default Main;
Tell me pls how nested screens works, because i have an error :
Error: Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app
Component Map:
export const Map = () => {
return (
<View style={styles.container}>
<MapView
provider={PROVIDER_GOOGLE} // remove if not using Google Maps
style={styles.map}
region={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.015,
longitudeDelta: 0.0121,
}}
>
<Marker
title="title"
description='descr'
coordinate={{
latitude: 37.78825,
longitude: -122.4324,
}}>
<View style={{backgroundColor: "red", padding: 10}}>
<Text>SF</Text>
</View>
</Marker>
</MapView>
</View>
);
};
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
height: '100%',
width: '100%',
justifyContent: 'flex-end',
alignItems: 'center',
},
map: {
...StyleSheet.absoluteFillObject,
},
});
problem was in wrong import MapView!

How to Overlay selected custom marker in react-native-maps

I use react-native-maps to display an array of positions using custom marker. I want to overlay the selected maker, but did not found the right solution. Using position: 'absolute' for the selected marker changes its position in the map.
Here is the code of the MapView:
<MapView
style={styles.map}
region={region} // initial region is user location if myPosition or coordinates of search
// onRegionChange={onRegionChange}
>
<View style={{position: 'absolute'}}>
{data.map((item) => (
<Marker
onPress={() => handleMarkerPress(item)}
coordinate={{
latitude: item.latitude,
longitude: item.longitude,
}}
// image={require('Resources/geoloc.png')}
// pinColor={item.id === visibleItemId ? 'red' : 'blue'}
key={item.id}>
<MosqueMarquer
mosqueImage={item.images}
itemId={item.id}
visibleItemId={visibleItemId}
distance={item.distance_result}
/>
</Marker>
))}
{isUserGeoLoc ? (
<Marker
coordinate={{
latitude: userLocation.latitude,
longitude: userLocation.longitude,
}}
description={'Your are here'}>
<Image
resizeMode="contain"
source={require('Resources/user_location.png')}
style={{tintColor: '#4280ee', height: 25}}
/>
</Marker>
) : null}
</View>
</MapView>
And here is the custom marker code:
const MosqueMarquer = (props) => {
const relativeStyle =
props.itemId == props.visibleItemId
? {position: 'absolute', tintColor: '#428947', color: '#fff', zIndex: 1}
: {position: null, tintColor: '#fff', color: '#3c423d', zIndex: 0};
return (
<ImageBackground
resizeMode="contain"
source={require('Resources/square_marker.png')}
style={{
...styles.imageBackground,
position: relativeStyle.position,
zIndex: relativeStyle.zIndex,
}}
imageStyle={{tintColor: relativeStyle.tintColor}}>
<Text style={{...styles.text, color: relativeStyle.color}}>
{props.distance}m
</Text>
</ImageBackground>
);
};
export default MosqueMarquer;
const styles = StyleSheet.create({
imageBackground: {
width: 70,
height: 70,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: common.FONT_SIZE_H38,
},
});
the selected marker in green is under the none selected one:
THANKS
<XMarksTheSpot coordinates={coordinatesOfYOurMarker} center={center} />
XMarksTheSpot ->
import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { Polygon, Polyline, Marker } from 'react-native-maps';
class XMarksTheSpot extends React.Component {
render() {
return (
<View>
<Polygon
coordinates={this.props.coordinates}
strokeColor="rgba(0, 0, 0, 1)"
strokeWidth={3}
/>
<Polyline
coordinates={[this.props.coordinates[0], this.props.coordinates[2]]}
/>
<Polyline
coordinates={[this.props.coordinates[1], this.props.coordinates[3]]}
/>
<Marker coordinate={this.props.center} />
</View>
);
}
}
XMarksTheSpot.propTypes = {
coordinates: PropTypes.array,
center: PropTypes.object,
zIndex: PropTypes.number,
};
export default XMarksTheSpot;

componentWillReceiveProps not triggering from child screen inside Tabview

I'm new in React native here. I got stuck on this scenario. I have a Dashboard screen. Inside dashboard screen, there's Hitcher screen that has Tabview. And there are HitcherTrip and HitcherChat inside the Tabview. In HitcherTrip, i expected componentWillReceiveProps() will trigger after calling Actions.goToOtherLayout() but componentWillReceiveProps() is triggered on Dashboard screen (Parent screen).
Dashboard
const Menu = createDrawerNavigator(
{
First: { screen: Hitcher },
Second: { screen: Driver }
},
{
contentComponent: props => (
<ScrollView>
<View style={{ padding:20, backgroundColor:'#4ca858' }}>
<Image source={require('../assets/pp.png')} style={{ borderRadius: 40, borderWidth: 1, borderColor: '#fff',width:80, height: 80 }} />
<CustomDrawerText/>
</View>
<SafeAreaView forceInset={{ top: "always", horizontal: "never" }}>
<Drawer.Item
label="I am Hitcher"
style={styles.drawerItem}
onPress={
() => {}
}
/>
<View style={{ height:1, backgroundColor:'#a8a8a8', marginLeft: 15, marginRight: 15 }}/>
<Drawer.Item
label="I am Driver"
onPress={
() => {}
}
style={styles.drawerItem}
/>
<View style={{ height:1, backgroundColor:'#a8a8a8', marginLeft: 15, marginRight: 15 }}/>
<Drawer.Item
label="Settings"
style={styles.drawerItem}
/>
<View style={{ height:1, backgroundColor:'#a8a8a8', marginLeft: 15, marginRight: 15 }}/>
<Drawer.Item
label="Log Out"
style={styles.drawerItem}
onPress={
() => {}
}
/>
<View style={{ height:1, backgroundColor:'#a8a8a8', marginLeft: 15, marginRight: 15 }}/>
</SafeAreaView>
</ScrollView>
)
}
);
const AppNav = createAppContainer(Menu);
export default class Dashboard extends React.Component {
constructor(props) {
super(props)
}
componentWillReceiveProps(props) {
console.log("Dashboard");
}
render() {
return(
<AppNav />
)
}
}
Hitcher
export default class Hitcher extends React.Component {
state = {
index: 0,
routes: [
{ key: 'first', title: 'My Trips' },
{ key: 'second', title: 'Chats' },
],
};
async _storeItem(key, token) {
try {
var token = await AsyncStorage.setItem(key, token);
return token;
} catch (error) {
console.log(error.message);
}
}
render() {
return (
<View style={styles.container}>
<Appbar.Header
style={{ backgroundColor: '#4ca858' }}>
<Appbar.Action
icon="menu"
color="white"
onPress={() =>
this.props.navigation.dispatch(DrawerActions.toggleDrawer())
}
/>
<Appbar.Action icon={require('../assets/logo_inverted.png')} style={{flex:1, alignSelf:'center'}} size={65} color="white"/>
<Appbar.Action icon="bell" color="white"/>
</Appbar.Header>
<View style={styles.container}>
<TabView
style={{ marginTop: 10 }}
navigationState={this.state}
renderScene={SceneMap({
first: HitcherTrip,
second: HitcherChat,
})}
renderTabBar={props =>
<TabBar
{...props}
labelStyle={styles.label}
indicatorStyle={styles.indicator}
style={styles.tabbar}
getLabelText={({ route }) => route.title}
/>
}
onIndexChange={index => this.setState({ index })} />
</View>
</View>
);
}
}
HitcherTrip
export default class HitcherTrip extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
isFetching: false,
spinner: false,
itemId: 0
};
}
componentDidMount() {
this.getTrips();
}
getTrips = () => {
// codes
};
componentWillReceiveProps(props) {
console.log("HitcherTrip");
}
onRefresh() {
this.setState({isFetching: true,},() => {this.getTrips();});
}
createTrip = () => {
Actions.goToOtherLayout();
};
render() {
return(
<View style={styles.scene} >
<FlatList
showsVerticalScrollIndicator={false}
data={this.state.data}
renderItem={({item}) => {
var date = Moment(item.created_at).format('DD MMM');
var now = Moment();
var expired = Moment(item.expired_at);
var status = item.status_name;
if (now > expired && item.status == 0)
status = "Expired";
return (
<TouchableOpacity onPress={
() => {
}
}>
<Spinner
visible={this.state.spinner}
textContent={'Loading...'}
textStyle={styles.spinnerTextStyle}
/>
<View style={styles.container}>
{item.status == 0 ?
<View style={styles.header}>
<Text style={styles.headerLeftActive}>{status}</Text>
<Text style={styles.headerRightActive}>{item.request_no}</Text>
</View> :
<View style={styles.headerInActive}>
<Text style={styles.headerLeftInactive}>{status}</Text>
<Text style={styles.headerRightInactive}>{date}</Text>
</View>
}
<View style={styles.content}>
<Image source={require('../assets/fromto.png')} style={styles.image} />
<View style={styles.textContent}>
<Text style={styles.address}>{item.pickup_location}</Text>
<View style={{flex: 1}} />
<Text style={styles.address}>{item.dropoff_location}</Text>
</View>
</View>
</View>
</TouchableOpacity>
)}
}
keyExtractor={item => item.request_no}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isFetching}
/>
<TouchableOpacity
style={{
alignItems:'center',
justifyContent:'center',
width:70,
position: 'absolute',
bottom: 10,
right: 10,
height:70,
backgroundColor:'#4ca858',
borderRadius:100,
elevation: 6,
}} onPress={this.createTrip}>
<Text style={{color:'#fff', fontSize: 32}}>+</Text>
</TouchableOpacity>
</View>
)
}
}
On other layout, i have set
Actions.pop(); setTimeout(()=> Actions.refresh(), 500);
to trigger componentWillReceiveProps() when press back button. But it only triggers on Dashboard screen.
How to trigger componentWillReceiveProps() on HitcherTrip? Or maybe trigger HitcherTrip function from Dashboard screen?

Hoisting react-navigation static navigationOptions

I have few class components, that uses react-navigation, when I'm wrapping class component to use HOC, the header dissappears, after doing some research, it seems I need to hoist the static navigationOptions = {}
My themeProvider
export const ThemeContextProvider = ({ children }) => {
const [themeID, setThemeID] = useState();
useEffect(() => {
(async () => {
const storedThemeID = await AsyncStorage.getItem(STORAGE_KEY);
if (storedThemeID) setThemeID(storedThemeID);
else setThemeID(THEMES[1].key);
})();
}, []);
return (
<ThemeContext.Provider value={{ themeID, setThemeID }}>
{!!themeID ? children : null}
</ThemeContext.Provider>
);
};
export function withTheme(Component) {
return props => {
const { themeID, setThemeID } = useContext(ThemeContext);
const getTheme = themeID => THEMES.find(theme => theme.key === themeID);
const setTheme = themeID => {
AsyncStorage.setItem(STORAGE_KEY, themeID);
setThemeID(themeID);
};
return (
<Component
{...props}
themes={THEMES}
theme={getTheme(themeID)}
setTheme={setTheme}
/>
);
};
hoistNonReactStatics(withTheme, HomeScreen); //I've tried this, but header still does not show up.
}
The component in question
export class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'Dashboard',
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'red',
borderBottomWidth: 0,
},
headerLeft: (
<TouchableOpacity
style={{ paddingLeft: 15 }}
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
>
<Feather name="arrow-left" size={24} color="#ffffff" />
</TouchableOpacity>
),
headerRight: (
<View style={{ flexDirection: 'row' }}>
</View>
),
});
componentDidMount() {
...
}
render() {
const { theme } = this.props;
console.log(theme);
return this.state.loading ? (
<ActivityIndicator
color="red"
size="large"
style={{ alignSelf: 'center', flex: 1 }}
/>
) : (
<View style={[styles.container, { backgroundColor: theme.backgroundColor }]}>
<View style={styles.container2}>
<TouchableOpacity>
<Feather
style={{ top: '60%', left: '28%' }}
name="plus"
size={32}
color="#ffffff"
onPress={this._openNewTaskModal}
/>
</TouchableOpacity>
<TouchableOpacity>
</TouchableOpacity>
</View>
<Feather
style={{ bottom: '5%', left: '85%' }}
name="calendar"
size={22}
color="#ffffff"
/>
<Feather
style={{ bottom: '9%', left: '8%' }}
name="home"
size={22}
color="#ffffff"
/>
</View>
);
}
}
export default withTheme(HomeScreen);
});
I also tried to export it as hoistNonReactStatics in the HomeScreen but no luck, what am I missing?
The solution was to use
export default hoistNonReactStatics(withTheme(HomeScreen), HomeScreen);

Open ListView Photos in Modal

I want to use Modal with ListView and open That in renderRow(rowData){} Area But Does not open.
That is okey in render() Area But I Got: a red Screen Can't Find Variable: rowData
How Can I Fix That?
My Code:
import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableHighlight, ListView, Image, Modal, Linking } from 'react-native';
import photosData from '../dataset/Photos'
var Dimensions = require('Dimensions')
var { width, height } = Dimensions.get('window')
export default class MyListView extends React.Component {
constructor(props) {
super(props)
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 })
this.state = {
dataSource: ds.cloneWithRows(photosData),
modalVisible: false,
}
}
setModalVisible(visible) {
this.setState({ modalVisible: visible });
}
renderRow(rowData) {
const img = rowData.image
return (
<TouchableHighlight style={styles.containerCell}
// onPress={() => Linking.openURL(img)}
onPress={() => { this.setModalVisible(true) }}
>
<View>
<Image
// resizeMode={Image.resizeMode.contain}
// resizeMethod={"scale"}
style={{ width: width, height: 180, }}
source={{ uri: img }}
/>
<View style={styles.footerContainer}>
<View
style={styles.imageUser}
>
<Image
style={styles.imageAvatar}
// source={{ uri: rowData.user }}
source={require('../assets/icons/footer-avatar.png')}
/>
</View>
<View style={styles.footerTextContainer}>
<Text style={{ color: 'blue' }} //I can see my photos in webview
onPress={() => Linking.openURL(img)}>
Google
</Text>
<Text style={styles.text}>{rowData.food}</Text>
<Text style={[styles.text, styles.textTitle]}>{rowData.title}</Text>
<Text style={[styles.text, styles.textBy]}>By {rowData.by}</Text>
</View>
</View>
</View>
</TouchableHighlight>
)
}
render() {
const img = rowData.image
return (
<View style={styles.container}>
<Modal
animationType={"slide"}
transparent={false}
visible={this.state.modalVisible}
onRequestClose={() => { alert("Modal has been closed."), this.setModalVisible(!this.state.modalVisible) }}
>
<View style={{ marginTop: 22 }}>
<View>
<Image
// resizeMode={Image.resizeMode.contain}
// resizeMethod={"scale"}
style={{ width: width, height: 180, }}
source={{ uri: img }} // I can'ttttttttttt see my photos in Modal
/>
<TouchableHighlight onPress={() => {
this.setModalVisible(!this.state.modalVisible)
}}>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
<ListView
style={styles.listContainer}
renderRow={this.renderRow.bind(this)}
dataSource={this.state.dataSource}
/>
</View>
);
}
}
I have pasted your code, it seem you have minor syntax error of closing bracket but I hope it is just copy-paste mistake.
First thing, you should not place your your Modal in renderRow function, it can create problem sometimes with styling and data. You can place it in main render() method.
Add function call to your rendering method if data is available and set this.setModalVisible(true).
Example:
render() {
return(
<View>
{/* other code */}
<Modal
animationType={'slide'}
transparent={false}
visible={this.state.modalVisible}
onRequestClose={() => { this.setModalVisible(false); } }
>
<View style={{ marginTop: 22 }}>
{(/*add some condition to check availability of data */)
? this.renderRow() // rendering function
: NULL}
</View>
</Modal>
</View>
);
}
EDIT after OP changed question:
Please find full code with my comment (not tested just added logic to work-around)
import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableHighlight, ListView, Image, Modal, Linking } from 'react-native';
import photosData from '../dataset/Photos'
var Dimensions = require('Dimensions')
var { width, height } = Dimensions.get('window')
export default class MyListView extends React.Component {
constructor(props) {
super(props)
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 })
this.state = {
dataSource: ds.cloneWithRows(photosData),
modalVisible: false,
currentImage: ''
}
}
setModalVisible(visible, img) {
this.setState({ modalVisible: visible, currentImage: img }); // set current image path to show it in modal
}
renderRow(rowData) {
const img = rowData.image
return (
<TouchableHighlight style={styles.containerCell}
// onPress={() => Linking.openURL(img)}
onPress={() => { this.setModalVisible(true, img) }} // pass image scr to function
>
<View>
<Image
// resizeMode={Image.resizeMode.contain}
// resizeMethod={"scale"}
style={{ width: width, height: 180, }}
source={{ uri: img }}
/>
<View style={styles.footerContainer}>
<View
style={styles.imageUser}
>
<Image
style={styles.imageAvatar}
// source={{ uri: rowData.user }}
source={require('../assets/icons/footer-avatar.png')}
/>
</View>
<View style={styles.footerTextContainer}>
<Text style={{ color: 'blue' }} //I can see my photos in webview
onPress={() => Linking.openURL(img)}>
Google
</Text>
<Text style={styles.text}>{rowData.food}</Text>
<Text style={[styles.text, styles.textTitle]}>{rowData.title}</Text>
<Text style={[styles.text, styles.textBy]}>By {rowData.by}</Text>
</View>
</View>
</View>
</TouchableHighlight>
)
}
render() {
// const img = rowData.image
return (
<View style={styles.container}>
<Modal
animationType={"slide"}
transparent={false}
visible={this.state.modalVisible}
onRequestClose={() => { alert("Modal has been closed."), this.setModalVisible(!this.state.modalVisible) }}
>
<View style={{ marginTop: 22 }}>
<View>
<Image
// resizeMode={Image.resizeMode.contain}
// resizeMethod={"scale"}
style={{ width: width, height: 180, }}
source={{ uri: this.state.currentImage }} // use currentImage scr to show on clicking list item
/>
<TouchableHighlight onPress={() => {
this.setModalVisible(!this.state.modalVisible)
}}>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
<ListView
style={styles.listContainer}
renderRow={this.renderRow.bind(this)}
dataSource={this.state.dataSource}
/>
</View>
);
}
}