react-native-maps, rendering dynamic markers post run-time - marker

Delete this question please, someone has helped me on another thread.
I am trying to generate markers at certain times in my app so that it will show a blank map at launch, but then sometime later, markers can show up and disappear at certain times.
My state variables are set up like this:
class Map extends Component {
state = {
markers: [{
key: null,
contactName: null,
location: null,
}]
}
The code I am trying to generate the markers with is as follows:
renderMarker = ({ key, contactName, location }) => {
console.log('renderMarker: ' + key + ', ' + contactName + ', ' + location);
return this.state.markers.map(Marker => (
<MapView.Marker
key = { key }
coordinate = {{
latitude: location[0],
longitude: location[1]
}}
//image = { carIcon }
title = { contactName } />
))
}
The console.log in there is generating the correct data, so it is returning for example:
renderMarker: 389djhJHDjdkh392sdk, Steve, -30.498767387,120.4398732
My render function looks like this:
render() {
return (
<MapContainer>
<MapView
style = { styles.map }
region = { this.state.mapRegion }
showsUserLocation = { true }
followUserLocation = { true }
onRegionChangeComplete = { this.onRegionChangeComplete.bind(this) }>
</MapView>
</MapContainer>
)
}
But nothing shows up on the map. I have been scouring the interwebs but I can't find a followable example. Do I need to add something to my render() function to make this work? My thoughts were I won't need to because renderMarker is doing that work.
Any ideas guys?

Related

Render Polygon only if within MapView in React Native

I have a long list of areas loaded from a SQLite database into react-native-maps. Each area has a particular status, and a fillColor that gets calculated based on its status. As some areas end up having hundreds of Polygons, the maps gets slower the more areas are calculated. Is there a way to detect if a Polygon is within the MapView to hide it when is not within the observable map?
So far I have a memoized component that gets the coordinates and a couple of helper function to get the color based on the area. The looped component gets then injected inside the MapView.
const Areas = useMemo(
() =>
areas &&
areas.map((area: Area) => {
console.log('rendering');
const coordinates = getCoordinates(area.geometry);
const areaColor = getAreaColor(area.status);
return (
<View key={area.CODE}>
<Polyline
strokeWidth={2}
fillColor={'transparent'}
coordinates={coordinates}
strokeColor={areaColor}
tappable={true}
onPress={() => handlePressArea(area)}
/>
</View>
);
}),
[areas]
);
const getCoordinates = (coords: string): LatLng[] => {
const geometry = JSON.parse(coords);
const output = geometry[0].map((item: Point[]) => ({
latitude: item[1],
longitude: item[0],
}));
return output;
};
const getAreaColor = (status: Area['status']): string => {
if (status === STATUS.ONE) {
return 'rgba(54, 155, 247, 0.6)';
} else if (status === STATUS.TWO) {
return 'rgba(240, 54, 0, 0.6)';
} else {
return 'rgba(235, 149, 50, 0.6)';
}
};
Eventually I mount the component like this:
<MapView
initialRegion={region}
provider={PROVIDER_DEFAULT}
rotateEnabled={false}
style={styles.map}
showsUserLocation={true}
onRegionChangeComplete={handleRegionChange}
customMapStyle={mapStyle}
>
{AreasView}
</MapView>
I tried to get a ref for each Polygon but the number of callbacks is so high that makes the app crash. I also tried with ObservableAPI but with no success. Simplifying the polygons is not an option either. Is there any way to detect if each polygon is within the observable map to hide them when out of view? I been stuck for some days so far, I hope someone could help.
Thank you!
UPDATE
I have implemented a function that checks if a polygon is within boundaries on region change as such:
<MapView
...props
ref={mapRef}
onRegionChangeComplete={getMapBounds}
/>
const mapRef = useRef<MapView>(null);
const getMapBounds = async () => {
await mapRef?.current?.getMapBoundaries().then(res => setMapBounds(res));
};
This basically stores inside the state the northEast and southWest boundaries of the map every time the user changes view.
Then to check if is in view:
const isInView = useMemo(
() => (point: LatLng) => {
if (mapBounds) {
const { northEast, southWest } = mapBounds;
const x = point.longitude;
const y = point.latitude;
return (
northEast.latitude >= y &&
y >= southWest.latitude &&
northEast.longitude >= x &&
x >= southWest.longitude
);
}
},
[mapBounds]
);
Gets a portion of the polygon and checks is the latitude and longitude of the point are within those boundaries
And so this leaves to:
const AreasView = useMemo(
() =>
areas &&
areas.map((area: Area) => {
const coordinates = getCoordinates(area.geometry);
const areaColor = getAreaColor(area.status);
const isVisible = isInView(coordinates[0]);
if (isVisible) {
return (
<View key={area.CODE}>
<Polygon
strokeWidth={2}
fillColor={areaColor}
coordinates={coordinates}
strokeColor={areaColor}
tappable={true}
onPress={() => handlePressArea(area)}
/>
</View>
);
}
}),
[areas, isInView]
);
It's still quite slow...shall I use useCallback instead, or is it any function for any library that is more optimised than that?
Cheers

Suggestion for fetch the data in database and set progress bar

I have already stored the student id and number of books read in the database. now, I have implemented the method to get the details of books count read by the students. but the function progressbardata() doesn't return the data fetched from query. I have tried to assign a value manually inside the db.transaction() and returned it but no luck.
Could you please let me know where i am doing wrong. also this method looping multiple times. how can we sort this out.
import * as Progress from 'react-native-progress';
import { openDatabase } from 'react-native-sqlite-storage';
var db = openDatabase({ name: 'UserDatabase.db' });
let progressbardata = (id) => {
var totalItems=0;
db.transaction((tx) => {
tx.executeSql('SELECT * FROM student where id = ?', [id], (tx, results) => {
totalItems = results.rows.item(0).percent;
return (totalItems);
})
})
}
const _renderContent = (item, index) => {
return (
<View>
<Progress.Bar color='#68FF33' progress={progressbardata(item.id)}/>
</View>
)
}
My guess would be that the request to fetch the data is asynchronous and this code does not seem to wait for a response to continue. Therefor the code would continue while the fetch requests has not returned a value yet. If this is the case, I would suggest that you let the request set the State of the component, and make the state the input of the Progress.Bar component. This way, when the fetch requests finishes asynchronously, the state will be set and the progress bar will be updated. This would look something like this:
import * as Progress from 'react-native-progress';
import { openDatabase } from 'react-native-sqlite-storage';
import { useState } from 'react'
var db = openDatabase({ name: 'UserDatabase.db' });
const renderContent = (props) => {
[progress, setProgress] = useState(0)
let progressbardata = (id) => {
var totalItems=0;
db.transaction((tx) => {
tx.executeSql('SELECT * FROM student where id = ?', [id], (tx, results) => {
totalItems = results.rows.item(0).percent;
setProgress(totalItems);
})
})
}
progressbardata(props.id)
return (
<View>
<Progress.Bar color='#68FF33' progress={progress}/>
</View>
)
}

How to replace #mention in string with a Text Componet with link - React Native

I am using React Native. What I need to do is from a text that I extract from the DB to apply a format (font color, link) to make #mentions, when searching if in the text finds 1 single match makes replacement all good, but if there are several #mentions in the text, it throws me an error.
Text example:
hey what happened #-id:1- everything ok ?? and you #-id:2- #-id:3- #-id:4-
//// listusers its array, example: [idusuario: "1", usuario: "#luigbren", format: "#-id:1-".....]
const PatternToComponent = (text, usuarios,) => {
let mentionsRegex = new RegExp(/#-(id:[0-9]+)-/, 'gim');
let matches = text.match(mentionsRegex);
if (matches && matches.length) {
matches = matches.map(function (match, idx) {
let usrTofind = matches[idx]
//////////////////////////////////////////////////////////////////
const mentFormat = listusers.filter(function (item) {
const itemData = item.format;
return itemData.indexOf(usrTofind) > -1;
});
if (mentFormat.length > 0) {
let idusuario = mentFormat[0].idusuario
let replc = mentFormat[0].usuario
console.log(usrTofind) //// here find #-id:1-
console.log(replc) //// here is #luigbren for replace
////////// now here replace part of the string, #-id:1- with a <Text> component
///////// with #luigbren and the link, this is repeated for every #mention found
parts = text.split(usrTofind);
for (var i = 1; i < parts.length; i += 2) {
parts[i] = <Text key={i} style={{ color: '#00F' }} onPress={() => { alert('but this is'); }}>{replc}</Text>;
}
return text = parts;
/////////////////////////////////////////////////////////////////////
} else {
return text
}
});
} else {
return text
}
return text
};
in this way the code works well for me only when in the text there is only one mention, example 'hey what happened #-id:1- everything ok ??' , but when placing more than one mention it gives me an error, example: 'hey what happened #-id:1- everything ok ?? #-id:2- #-id:3-' ...
Error:
TypeError: text.split is not a function
And if instead of placing parts = text.split(usrTofind); I place parts = text.toString().split(usrTofind); it gives me this error:
[Object Object]
Update
I found the solution. I found another simple way to replace part of the text with the <Text> component, using this method:
funcMentToLinks = (text, idusuario) => {
const regex = /\#[id:([0-9]+]/g;
const splitText = text.split(regex)
if (splitText.length <= 1) {
return text;
}
const matches = text.match(regex);
return splitText.reduce(function (arr, element, index) {
if (matches[index]) {
return [arr, element, <Text key={index} style={{ backgroundColor: '#ceedf1', fontWeight: 'bold', color: '#4f73c4' }} onPress={() => this.callmyopc(idusuario)}>{replc}</Text>,]
} else {
return [arr, element]
}
}, []);
}
but now I have a problem, I can't find a way to call a function to navigate to another section or invoke direct the this.props.navigation.navigate('opc'), if i put my function .. example: onPress={() => this.callmyopc(idusuario)} gives me an error
Error:
_this2.callmyopc is not a function
if i put it this way onPress={() => {this.props.navigation.navigate('opc', { idperf: idusuario })}} gives me an error..
Error:
Cannot read property 'navigation' of undefined
Note: the functions this.props.navigation.navigate if they work for me in this same file, I use it in other functions, but in this function I cannot invoke it.

How to use the $filter variable on graphql query under the Connect component?

I have a simple query auto-generated from aws AppSync, and I'm trying to use the Connect Component, with a FlatList and use a TextInput to filter and auto-update the list. But I confess I didn't found out a way to do that... any hints?
Tried to find more information about this without success...
Auto-Generated query:
export const listFood = `query ListFood(
$filter: ModelFoodFilterInput
$limit: Int
$nextToken: String
) {
listFood(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
name
description
...
My current code, which I don't quite know where to place my filter value:
<Connect query={graphqlOperation(queries.listFood)}>
{
( { data: { listFood }, loading, error } ) => {
if(error) return (<Text>Error</Text>);
if(loading || !listFood) return (<ActivityIndicator />);
return (
<FlatList
data={listFood.items}
renderItem={({item}) => {
return (
<View style={styles.hcontainer}>
<Image source={{uri:this.state.logoURL}}
style={styles.iconImage}
/>
<View style={styles.vcontainer}>
<Text style={styles.textH3}>{item.name}</Text>
<Text style={styles.textP}>{item.description}</Text>
</View>
</View>
);
}}
keyExtractor={(item, index) => item.id}
/>
);
}
}
</Connect>
What I aim is mainly to filter by item.name, refreshing the list while typing from a TextInput, probably going somewhere on the $filter variable...
Ok, I think I've figured out the usage with the AWS AppSync Out-of-the-box queries...
query MyFoodList{
listFood(
filter: {
name: {
contains:"a"
}
}
) {
items {
id
name
}
}
}
And it is finally working properly with this disposition on my react-native code:
<Connect query={ this.state.filter!=="" ?
graphqlOperation(queries.listFood, {
filter: {
name: {
contains: this.state.filter
}
}
})
:
graphqlOperation(queries.listFood)
}>
I still didn't manage to make the sort key work yet... will try a little more and open another topic for it if I didn't get anything...
This is filter in use in React / Javascript:
const [findPage, setFindPage] = useState('') // setup
async function findpoints() {
// find user & page if exists read record
try {
const todoData = await API.graphql(graphqlOperation(listActivitys, {filter : {owner: {eq: props.user}, page: {eq: action}}}))
const pageFound = todoData.data.listActivitys.items // get the data
console.log('pageFound 1', pageFound)
setFindPage(pageFound) // set to State
} catch (err) {
console.log(err)
}
}
The async / wait approach means the code will try to operate, and move on to other areas of your code putting data into findPage through setFindPage when and if it finds data

Fetching multiple API requests with React Native

Here is an outline of my code (sparing some details). Basically I just want to make two similar API requests when I click a button, then have a function that works with the results of both the requests, but I cannot figure it out.
class myClass extends Component {
constructor(props) {
super(props);
this.API_KEY_ONE = ‘firstapikey’
this.API_KEY_TWO = ‘secondapikey’
this.state = {
city: 'undefined',
state: 'undefined'
}
}
callOne() {
this.REQUEST_URL = 'https://api.wunderground.com/api/' + this.API_KEY_ONE + '/geolookup/conditions/astronomy/forecast/q/.json';
fetch(this.REQUEST_URL).then((response) => response.json()).then((responseData) => {
this.setState({
city: responseData.location.city
});
}).done();
}
callTwo() {
this.REQUEST_URL = 'https://api.DifferentSite.com/api/' + this.API_KEY_TWO + '/geolookup/conditions/astronomy/forecast/q/.json';
fetch(this.REQUEST_URL).then((response) => response.json()).then((responseData) => {
this.setState({
state: responseData.location.state
});
}).done();
}
// where to put this? when both requests finish, pass both to new component
this.props.navigator.push({
title: 'Forecast',
component: Forecast,
passProps: {city: this.state.city, state: this.state.state}
});
getForecast() {
this.callOne();
this.callTwo();
}
<TouchableHighlight onPress={() => this.getForecast()} />
You can continue with .then() so it should be something like this:
callBoth(){
var request_1_url = 'https://api.wunderground.com/api/' + this.API_KEY_ONE + '/geolookup/conditions/astronomy/forecast/q/.json';
var request_2_url = 'https://api.DifferentSite.com/api/' + this.API_KEY_TWO + '/geolookup/conditions/astronomy/forecast/q/.json';
fetch(request_1_url).then((response) => response.json()).then((responseData) => {
this.setState({
city: responseData.location.city
});
}).then(()=>{
fetch(request_2_url).then((response) => response.json()).then((responseData) => {
this.setState({
state: responseData.location.state,
isFinish: true
});
}).done();
}).done();
}
1) It seems you are using city and state as passProps and not going to refresh the currentView, so maybe you should use them as variables of the current component.
2) You can simply use a variable to record the state of fetching. Like set _finish = 0, when city is fetched, _finish = _finish + 1, and validate whether _finish equals 2. When state is fetched, do the same validate.
fetch(...){
// if _finish is equals 2, navigator push.
}
3) Or you can do it without extra variable:
fetch(...){
if (this._city && this._state){
// navigator push
}
}