I used react-native-maps in my location tracking application. And now i want to clear ( refresh ) the map with all the markers and poly-lines without refreshing react component , when user stop the location Tracking ( using a button ).Is there any way to do that?
At some point if you wish to change what is visible on your component in react, there's no other way than rerendering your component.
I do not know react-native-maps, but I think this problem can be solved using general react knowledge.
Markers and polygons in react-native-maps are added as children of the MapView, so depending on how you are storing your markers/lines, you should be able to simply rely on react's state or props changes to update your MapView.
For example, if your markers are stored in your component's state, you could do something like this:
class MyLocationTracker extends React.Component {
state = { markers: [{ latlng: { latitude: 1, longitude: 1 } }] }
render = () => {
return (
<View>
<Text onPress={() => this.setState({ markers: [] })}>Reset markers</Text>
<MapView>
{this.state.markers.map((marker) => <Marker coordinate={marker.latlng} />)}
</MapView>
</View>
)
}
}
When the state changes, react will call the render() function again and update the markers. Note that this will also serenader the <MapView /> component.
If you wish to avoid rerendering the <MapView /> component, you can move the logic to a new component that will handle the markers only, like so:
class MyLocationTracker extends React.Component {
render = () => {
return (
<View>
<Text onPress={this.markers.reset}>Reset markers</Text>
<MapView>
<MyMarkers ref={(comp) => { this.markers = comp }} />
</MapView>
</View>
)
}
}
class MyMarkers extends React.Component {
state = { markers: [{ latlng: { latitude: 1, longitude: 1 } }] }
reset = () => {
this.setState({ markers: [] })
}
render = () => {
return (
<View>
{this.state.markers.map((marker) => <Marker coordinate={marker.latlng} />)}
</View>
)
}
}
Again, changes in the state would trigger a rerender of your component. But now, since only <MyMarkers />'s state changes, it will rerender itself but not anything else.
I hope that helps :)
Related
I want to display an array of data in flatlist or scrollview in loop mode. I want to repeat the same array again and again. can we do that?
You can use onEndReached fo FlatList to load the data at the end. In your case,
append the same list and set the state.
onEndReached(): Called once when the scroll position gets within onEndReachedThreshold
of the rendered conten
export default class App extends React.Component {
state = {
movies: [
{
name: "ajd"
},
{
name: "wd"
}
]
};
onEndReached = () => {
console.log('onEndReached()', this.state.data)
this.setState({movies: [...this.state.movies, ...this.state.movies,]})
}
render() {
return (
<View >
<FlatList
data={this.state.movies}
renderItem={({item}) => {
return (
<View>
<Text>{item.name}</Text>
</View>
)
}}
onEndReached={this.onEndReached}
onEndReachedThreshold={0.5}
/>
</View>
);
}
}
I am writing an app using Expo (React Native framework). Whenever the state of a component is changed with setState(...) method, the component should re-render to show the latest state data. This works with basic React Native components such as View, Text, Button (I have tried these three), but it does not re-render here, where I use custom component TabView. This is stated in the documentation about component:
"All the scenes rendered with SceneMap are optimized using React.PureComponent and don't re-render when parent's props or states change. If you need more control over how your scenes update (e.g. - triggering a re-render even if the navigationState didn't change), use renderScene directly instead of using SceneMap."
I don't quite understand how to manage this. I would like the component to re-render whenever the state is changed, in this case, after clicking the button and calling the function writeData().
Documentation of the TabView component is here: https://github.com/react-native-community/react-native-tab-view .
import React from 'react';
import { TabView, SceneMap } from 'react-native-tab-view';
import { StyleSheet, Text, View, Dimensions, Button, ToastAndroid } from 'react-native';
export default class MojTabView extends React.Component {
state = {
index: 0,
routes: [
{ key: 'allEvents', title: 'Všetko' },
{ key: 'sortedEvents', title: 'Podľa druhu' },
{ key: 'myEvents', title: 'Moje akcie'}
],
name: "Robert"
};
MyEvents = () => (
<View style={[styles.scene, { backgroundColor: 'white' }]}>
<Text>Some content</Text>
<Button
style={{ margin: 5 }}
onPress={this.writeData}
title="Write data"
color="#841584"
/>
<Text>Name in state: {this.state.name}</Text>
</View>
);
SortedEvents = () => (
<Text>Some content</Text>
);
AllEvents = () => (
<Text>Some other content</Text>
);
writeData = () => {
ToastAndroid.show("button click works!", ToastAndroid.LONG);
this.setState({ name: "Richard"});
}
render() {
return (
<TabView
navigationState={this.state}
renderScene={SceneMap({
allEvents: this.AllEvents,
myEvents: this.MyEvents,
sortedEvents: this.SortedEvents
})}
onIndexChange={index => this.setState({ index })}
initialLayout={{ width: Dimensions.get('window').width }}
/>
);
}
}
I spent a few hours trying to achieve that, without solution. This is my first StackOverflow question, thanks.
Okay, I was finally able to come up with a nice solution. The point is to define content of individual tabs/routes in a separate file as a typical React Native component with its own state, not inside this MyTabView component, as it is made even in the example in the documentation about TabView. Then it works as it should.
Simple example:
This is content of one of the tabs:
import React from 'react';
import { Text, View, Button } from 'react-native';
export default class MyExampleTab extends React.Component {
state = {
property: "Default value",
}
writeData = () => {
this.setState({
property: "Updated value"
})
}
render() {
return (
<View>
<Button
style={{ margin: 5 }}
onPress={this.writeData}
title="Change the state and re-render!"
color="#841584"
/>
<Text>Value of state property: {this.state.property}</Text>
</View>
)
}
}
This is how I reference it in the MyTabView component:
import MyExampleTab from './MyExampleTab'
...
export default class MyTabView extends React.Component {
...
render() {
return (
<TabView
navigationState={this.state}
renderScene={SceneMap({
...
someKey: MyExampleTab,
...
})}
onIndexChange={index => this.setState({ index })}
initialLayout={{ width: Dimensions.get('window').width }}
/>
)
So I don't use <MyExampleTab />, as I would normally using custom component, but write it as it is, MyExampleTab. Which quite makes sense. And on the button click the state changes and tab re-renders.
I changed to self implemented renderScene and inner component now get re-rendered
<TabView
navigationState={{ index, routes }}
renderScene={({ route }) => {
switch (route.key) {
case 'first':
return <FirstRoute data={secondPartyObj} />;
case 'second':
return SecondRoute;
case 'third':
return ThirdRoute;
case 'forth':
return ForthRoute;
default:
return null;
}
}}
onIndexChange={setIndex}
initialLayout={{ width: layout.width }}
renderTabBar={renderTabBar}
/>
The two pieces of state that should triger re-render of your TabView are:
index.
routes
They've done that for performance optimization purposes
if you want to force re-rendering your TabView if you're changing any other state value that has nothing to do with these values ... then according to the docs ... you need to provide your own implementation for renderScene like:
renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'music':
return ;
case 'albums':
return ;
}
};
in this case:
You need to make sure that your individual routes implement a
shouldComponentUpdate to improve the performance.
Building on what #druskacik wrote above, you can actually pass a component with props too
import MyExampleTab from './MyExampleTab'
...
export default class MyTabView extends React.Component {
...
render() {
return (
<TabView
navigationState={this.state}
renderScene={SceneMap({
...
someKey:() => <MyExample foo={foodata} />,
...
})}
onIndexChange={index => this.setState({ index })}
initialLayout={{ width: Dimensions.get('window').width }}
/>
)
I want to make a responsive App in React Native. I subscribe to Dimension changes in the container using this:
const RCTDeviceEventEmitter = require("RCTDeviceEventEmitter");
export interface Props {
data: Array<any>;
}
export interface State {}
class MyContainer extends React.Component<Props, State> {
_updateIfSelected() {
if (...some more logic...) {
this.forceUpdate();
}
}
constructor(props) {
super(props);
this.state = {
listener: null,
updateIfSelected: this._updateIfSelected.bind(this),
};
}
componentWillMount() {
this.setState({
listener: RCTDeviceEventEmitter.addListener("didUpdateDimensions", this.state.updateIfSelected),
});
}
componentWillUnmount() {
this.state.listener.remove("didUpdateDimensions",this.state.updateIfSelected);
}
renderItem() {
console.log("Rerendering Item")
return <Text style={{ width: Dimensions.get("window").width }} >Some Text</Text>
}
render() {
return (
<FlatList
data={this.props.data}
keyExtractor={(_, i) => (i.toString())}
renderItem={({item}) => this.renderItem(item)}
/>
}
}
I was wondering how to force a FlatList to rerender its items, because the appearance needs to change when the screen is tilted. However, because the data doesn't change, the list won't be rerendered on screen tilts.
The Documentation provides a parameter called extraData:
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
But I don't really understand what this is trying to say. Any Ideas how I can make the FlatList Rerender on Dimension changes?
You can pass a function to onLayout and check the dimensions when onLayout is called. I would recommend passing the function to a parent view. You could then setState in the function
class MyComponent extends Component {
_onLayout() { // I haven't tried this but I think this or something similar will work
const { width, height } = Dimensions.get('window');
if(width > height) {
this.setState(state => ({ ...state, orientation: 'landscape' }));
} else {
this.setState(state => ({ ...state, orientation: 'portrait' }));
}
}
render() {
return (
<View onLayout={this._onLayout}>
<FlatList
data={this.props.data}
keyExtractor={(_, i) => (i.toString())}
renderItem={({item}) => this.renderItem(item)}
extraData={this.state.orientation}
/>
</View>
)
}
}
I followed this tutorial https://www.youtube.com/watch?v=rY0braBBlgw
When I scroll down it sends the request then it gets stuck in a loop and just requests and requests. I think this is a problem with the scrollview in the listview.
I am not sure if you were able to resolve this but I was having the same problem and I am adding what worked well for me.
onEndReachedThreshold=>onEndThreshold
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={
<Text style={{textAlign: 'left'}}> {item.name.first} {item.name.last}</Text>
}
subtitle={
<Text style={{textAlign: 'left'}}>{item.email}</Text>
}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
keyExtractor={item => item.email}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndThreshold={0}
/>
I hope this helps someone.
This works for me:
<FlatList
data={this.state.storesList}
renderItem={({ item, index }) => renderItem(item, index)}
keyExtractor={(item, index) => item.id.toString()}
onEndReached={this.fetchMore}
onEndReachedThreshold={0.1}
ListFooterComponent={this.renderFooter}
refreshing={this.state.refreshing}
/>
renderFooter = () => {
if (this.state.refreshing) {
return <ActivityIndicator size="large" />;
} else {
return null;
}
};
fetchMore = () => {
if (this.state.refreshing){
return null;
}
this.setState(
(prevState) => {
return { refreshing: true, pageNum: prevState.pageNum + 1 };
},
() => {
this.sendAPIRequest(null , true);
}
);
};
The reason I used the following in the fetchMore function:
if (this.state.refreshing){
return null;
}
Is because when you setState to the pageNum it calls the render() function and then the fetchMore called again. This is written to prevent it.
In addition, I set:
refreshing: false
after the sendAPIRequest is done.
Pay attention about onEndReachedThreshold in FlatList:
How far from the end (in units of visible length of the list) the
bottom edge of the list must be from the end of the content to trigger
the onEndReached callback.
Meaning in my example (0.1) means: when you reach 10% of items from the bottom, the fetchMore callback is called. In my example, I have 10 items in the list, so when the last item is visible, fetchMore is called.
I'm not sure if this is exactly what you're looking for, but the code I've left below allows you to continue scrolling through a fixed set of data props. When you reach the last index, it basically wraps around to the beginning. I've achieved this by appending a copy of the first element of the supplied data to the end of the FlatList; when the user scrolls this into view, we can safely reset the scroll offset.
import React, { Component } from 'react';
import { FlatList } from 'react-native';
export default class InfiniteFlatList extends Component {
constructor(props) {
super(props);
this.state = {
};
this._flatList = null;
}
getWrappableData = (data) => {
return [...data, data[0]];
}
render = () => (
<FlatList
{ ...this.props }
ref={ (el) => this._flatList = el }
onLayout={ ({nativeEvent}) => {
const {width, height} = nativeEvent.layout;
this.setState({
width, height
});
} }
onScroll={ ({ nativeEvent }) => {
const { x } = nativeEvent.contentOffset;
if(x === (this.props.data.length * this.state.width)) {
this._flatList.scrollToOffset({x: 0, animated: false});
}
} }
data={ this.getWrappableData(this.props.data) }
pagingEnabled={true}
/>
)
};
InfiniteFlatList.defaultProps = { };
InfiniteFlatList.propTypes = { };
This assumes you want to scroll horizontally.
It probably isn't perfect; there is likely a better technique out there which uses FlatList's onEndReached callback, however this only seemed to fire once througohout. By polling the scroll offset of the FlatList, we can fire off our own equivalent as many times as needed. If you specify a getItemLayout prop, you'll be able to use scrollToIndex({index, animated?}) instead.
Aug. 5, 2019 update
On React native 0.60, one should use scrollToOffset as:
this._flatList.scrollToOffset({offset: 0, animated: false});
I am currently working on an application that uses React Native Maps. I've seen their examples for putting multiple markers and how to change a marker's state when pressed, and I would like to be able to combine the two functions together. I want to be able to put down multiple markers, then change the state of individual markers when pressed. I've had success putting down multiple markers, but when pressed, all markers have their state changed. I'd like to know what to do so markers will have individual states changed when pressed. Thanks for all the help.
Here are the links to the examples of React Native Maps I used:
https://github.com/airbnb/react-native-maps/blob/master/example/examples/DefaultMarkers.js
https://github.com/airbnb/react-native-maps/blob/master/example/examples/MarkerTypes.js
Here's the code I currently have
const SPACE = 0.01;
let id = 0;
class MarkerTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
marker1: true,
region: {
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
},
markers: [],
};
}
onMapPress(e) {
console.log(e)
this.setState({
markers: [
...this.state.markers,
{
coordinate: e.nativeEvent.coordinate,
key: id++,
marker1: true,
},
],
});
}
render() {
return (
<View style={styles.container}>
<MapView
provider={this.props.provider}
style={styles.map}
initialRegion={this.state.region}
onPress={(e) => this.onMapPress(e)}
>
{this.state.markers.map(marker => (
<MapView.Marker
key={marker.key}
})}
onPress={() => {
this.setState({ marker1: !this.state.marker1 })}
}
coordinate={marker.coordinate}
image={this.state.marker1 ? flagBlueImg : flagPinkImg}
>
</MapView.Marker>
))}
</MapView>
</View>
);
}
}
To change the image marker on the marker that is touched on the map you need to toggle the marker property on the marker inside the this.state.markers array, eg this.state.markers[0].marker1, currently you are toggling the this.state.marker1 which is shared by all markers
{this.state.markers.map((marker, index) => (
<MapView.Marker
key={marker.key}
onPress={() => {
const marker = this.state.markers[index]
marker.marker1 = !marker.marker1
this.setState({ markers: [
...this.state.markers.slice(0, index),
marker,
...this.state.markers.slice(index + 1)
]})}
}
coordinate={marker.coordinate}
image={marker.marker1 ? flagBlueImg : flagPinkImg}
>
</MapView.Marker>
))}
in this way each marker is using and updating it's own state in the array.