Using React geolocation to perform geoqueries with Parse
Introduction
In this guide, you will perform geoqueries in Parse using the React geolocation. You will implement a React component using these queries and learn how to set up and query realistic data using Back4App and React.
Prerequisites
To complete this tutorial, you will need:
- A React App created and connected to Back4App.
- If you want to test/use the screen layout provided by this guide, you should set up the
Ant Design
library.
Goal
Perform Geoqueries using GeoPoints stored on Back4App and React geolocation.
Step 1 - Understanding the Parse.Query class
Any Parse query operation uses the Parse.Query
object type, which will help you retrieve specific data from your database throughout your app. It is crucial to know that a Parse.Query
will only resolve after calling a retrieve method (like Parse.Query.find
or Parse.Query.get
), so a query can be set up and several modifiers can be chained before actually being called.
To create a new Parse.Query
, you need to pass as a parameter the desired Parse.Object
subclass, which is the one that will contain your query results. An example query can be seen below, in which a fictional Profile
subclass is being queried.
1
2
3
4
// This will create your query
let parseQuery = new Parse.Query("Profile");
// The query will resolve only after calling this method
let queryResult = await parseQuery.find();
You can read more about the Parse.Query
class here at the official documentation.
Step 2 - Save some data on Back4App
Let’s create a City
class, which will be the target of our queries in this guide. On Parse JS Console it is possible to run JavaScript code directly, querying and updating your application database contents using the JS SDK commands. Run the code below from your JS Console and insert the data on Back4App.
Here is how the JS Console looks like in your dashboard:
Go ahead and create the City
class with the following example content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Add City objects and create table
// Note how GeoPoints are created, passing latitude and longitude as arguments
// Montevideo
var City = new Parse.Object('City');
City.set('name', 'Montevideo - Uruguay');
City.set('location', new Parse.GeoPoint(-34.85553195363169, -56.207280375137955));
await City.save();
// BrasĂlia
City = new Parse.Object('City');
City.set('name', 'BrasĂlia - Brazil');
City.set('location', new Parse.GeoPoint(-15.79485821477289, -47.88391074690196));
await City.save();
// Bogotá
City = new Parse.Object('City');
City.set('name', 'Bogotá - Colombia');
City.set('location', new Parse.GeoPoint(4.69139880891712, -74.06936691331047));
await City.save();
// Mexico City
City = new Parse.Object('City');
City.set('name', 'Mexico City - Mexico');
City.set('location', new Parse.GeoPoint(19.400977162618933, -99.13311378164776));
await City.save();
// Washington, D.C.
City = new Parse.Object('City');
City.set('name', 'Washington, D.C. - USA');
City.set('location', new Parse.GeoPoint(38.930727220189944, -77.04626261880388));
await City.save();
// Ottawa
City = new Parse.Object('City');
City.set('name', 'Ottawa - Canada');
City.set('location', new Parse.GeoPoint(45.41102167733425, -75.695414598736));
await City.save();
console.log('Success!');
Step 3 - Query the data
Now that you have a populated class, we can now perform some GeoPoint queries in it. Let’s begin by ordering City
results by the nearest from Kingston in Jamaica (latitude 18.01808695059913 and longitude -76.79894232253473), using the Parse.Query.near
method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Create your query
let parseQuery = new Parse.Query('City');
// Create our GeoPoint for the query
let kingstonGeoPoint = new Parse.GeoPoint(18.018086950599134, -76.79894232253473);
// `near` will order results based on distance between the GeoPoint type field from the class and the GeoPoint argument
parseQuery.near('location', kingstonGeoPoint);
// The query will resolve only after calling this method, retrieving
// an array of `Parse.Objects`
let queryResults = await parseQuery.find();
// Let's show the results
for (let result of queryResults) {
// You access `Parse.Objects` attributes by using `.get`
console.log(result.get('name'));
};
Let’s now query using the method Parse.Query.withinKilometers
, which will retrieve all results whose GeoPoint field is located within the max distance. Kingston will be used once again as a reference and the distance limit will be 3000 km.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Create your query
let parseQuery = new Parse.Query('City');
// Create our GeoPoint for the query
let kingstonGeoPoint = new Parse.GeoPoint(18.018086950599134, -76.79894232253473);
// You can also use `withinMiles` and `withinRadians` the same way,
// but with different measuring unities
parseQuery.withinKilometers('location', kingstonGeoPoint, 3000);
// The query will resolve only after calling this method, retrieving
// an array of `Parse.Objects`
let queryResults = await parseQuery.find();
// Let's show the results
for (let result of queryResults) {
// You access `Parse.Objects` attributes by using `.get`
console.log(result.get('name'));
};
Another useful query method is Parse.Query.withinPolygon
, which will query results whose GeoPoint field value is within the specified polygon, composed of an array of GeoPoints (at least three). If the polygon path is open, it will be closed automatically by Parse connecting the last and first points.
For this example, you will be using a simple polygon that roughly contains the South American continent, composed of 5 distant GeoPoints in the ocean.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Create your query
let parseQuery = new Parse.Query('City');
// Create our GeoPoint polygon for the query
let geoPoint1 = new Parse.GeoPoint(15.822238344514378, -72.42845934415942);
let geoPoint2 = new Parse.GeoPoint(-0.7433770196268968, -97.44765968406668);
let geoPoint3 = new Parse.GeoPoint(-59.997149373299166, -76.52969196322749);
let geoPoint4 = new Parse.GeoPoint(-9.488786415007201, -18.346101586021952);
let geoPoint5 = new Parse.GeoPoint(15.414859532811047, -60.00625459569375);
// Note that the polygon is merely an array of GeoPoint objects and that the first and last are not connected, so Parse connects them for you
parseQuery.withinPolygon('location', [geoPoint1, geoPoint2, geoPoint3, geoPoint4, geoPoint5]);
// The query will resolve only after calling this method, retrieving
// an array of `Parse.Objects`
let queryResults = await parseQuery.find();
// Let's show the results
for (let result of queryResults) {
// You access `Parse.Objects` attributes by using `.get`
console.log(result.get('name'));
};
Step 4 - Query from a React component
Let’s now use our example queries inside a component in React, with a simple interface having a list showing results and also 3 buttons for calling the queries. The component also retrieves the device’s current location using the navigator.geolocation
JavaScript API, so the queries will be using real data, provided that the user didn’t disabled location access in the browser.
This is how the component code is laid out, note the doQuery
functions, containing the example code form before.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import React, { useState } from 'react';
import Parse from 'parse/dist/parse.min.js';
import './App.css';
import { Button, Divider } from 'antd';
import { CloseOutlined, SearchOutlined } from '@ant-design/icons';
export const QueryGeo = () => {
// State variable
const [queryResults, setQueryResults] = useState();
const doQueryNear = async function () {
// Check if geolocation API is available in the browser
if ('geolocation' in navigator) {
// Get current location and create the GeoPoint for the query
navigator.geolocation.getCurrentPosition(
async function (position) {
const currentPosition = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
};
// Create our GeoPoint
let currentLocationGeoPoint = new Parse.GeoPoint(
currentPosition.latitude,
currentPosition.longitude
);
// Create our query
let parseQuery = new Parse.Query('City');
// `near` will order results based on distance between the GeoPoint type field from the class and the GeoPoint argument
parseQuery.near('location', currentLocationGeoPoint);
try {
let results = await parseQuery.find();
// Set query results to state variable
setQueryResults(results);
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
alert(`Error! ${error.message}`);
}
},
function (_error) {
alert(
'You need to allow geolocation to retrieve your location on the browser to test this. If you are on an Apple OS, enable it on your system preferences.'
);
}
);
} else {
alert('Geolocation services are not supported by your browser.');
}
return false;
};
const doQueryWithinKilometers = async function () {
// Check if geolocation API is available in the browser
if ('geolocation' in navigator) {
// Get current location and create the GeoPoint for the query
navigator.geolocation.getCurrentPosition(
async function (position) {
const currentPosition = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
};
// Create our GeoPoint
let currentLocationGeoPoint = new Parse.GeoPoint(
currentPosition.latitude,
currentPosition.longitude
);
// Create our query
let parseQuery = new Parse.Query('City');
// You can also use `withinMiles` and `withinRadians` the same way,
// but with different measuring unities
parseQuery.withinKilometers(
'location',
currentLocationGeoPoint,
3000
);
try {
let results = await parseQuery.find();
// Set query results to state variable
setQueryResults(results);
} catch (error) {
// Error can be caused by lack of Internet connection
alert(`Error! ${error.message}`);
}
},
function (_error) {
alert(
'You need to allow geolocation to retrieve your location on the browser to test this. If you are on an Apple OS, enable it on your system preferences.'
);
}
);
} else {
alert('Geolocation services are not supported by your browser.');
}
return false;
};
const doQueryWithinPolygon = async function () {
// Create our GeoPoint polygon points
let geoPoint1 = new Parse.GeoPoint(15.822238344514378, -72.42845934415942);
let geoPoint2 = new Parse.GeoPoint(-0.7433770196268968, -97.44765968406668);
let geoPoint3 = new Parse.GeoPoint(-59.997149373299166, -76.52969196322749);
let geoPoint4 = new Parse.GeoPoint(-9.488786415007201, -18.346101586021952);
let geoPoint5 = new Parse.GeoPoint(15.414859532811047, -60.00625459569375);
// Create our query
let parseQuery = new Parse.Query('City');
// Note that the polygon is merely an array of GeoPoint objects and that the first and
// last are not connected, so Parse connects them for you
parseQuery.withinPolygon('location', [
geoPoint1,
geoPoint2,
geoPoint3,
geoPoint4,
geoPoint5,
]);
try {
let results = await parseQuery.find();
// Set query results to state variable
setQueryResults(results);
} catch (error) {
// Error can be caused by lack of Internet connection
alert(`Error! ${error}`);
return false;
}
return true;
};
const clearQueryResults = async function () {
setQueryResults(undefined);
return true;
};
return (
<div>
<div className="header">
<img
className="header_logo"
alt="Back4App Logo"
src={
'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png'
}
/>
<p className="header_text_bold">{'React on Back4App'}</p>
<p className="header_text">{'GeoPoint Queries'}</p>
</div>
<div className="container">
<div className="flex_between">
<h2 className="heading">{'Query List'}</h2>
<div className="flex">
<Button
onClick={() => doQueryNear()}
type="primary"
className="heading_button"
color={'#208AEC'}
icon={<SearchOutlined />}
>
QUERY NEAR
</Button>
<Button
onClick={() => doQueryWithinKilometers()}
type="primary"
className="heading_button"
color={'#208AEC'}
icon={<SearchOutlined />}
>
QUERY WITHIN KM
</Button>
<Button
onClick={() => doQueryWithinPolygon()}
type="primary"
className="heading_button"
color={'#208AEC'}
icon={<SearchOutlined />}
>
QUERY WITHIN POLYGON
</Button>
<Button
onClick={() => clearQueryResults()}
type="primary"
className="heading_button"
color={'#208AEC'}
icon={<CloseOutlined />}
>
CLEAR RESULTS
</Button>
</div>
</div>
<Divider />
<div className="flex_between">
<div className="flex_child">
{/* Query list */}
{queryResults !== undefined &&
queryResults.map((result, index) => (
<div className="list_item" key={`${index}`}>
<p className="list_item_title">{`${result.get('name')}`}</p>
</div>
))}
{queryResults !== undefined && queryResults.length <= 0 ? (
<p>{'No results here!'}</p>
) : null}
</div>
</div>
</div>
</div>
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import React, { useState, FC, ReactElement } from 'react';
import './App.css';
import { Button, Divider } from 'antd';
import { CloseOutlined, SearchOutlined } from '@ant-design/icons';
const Parse = require('parse/dist/parse.min.js');
type LocationObject = {latitude: number, longitude: number};
export const QueryGeo: FC<{}> = (): ReactElement => {
// State variable
const [queryResults, setQueryResults] = useState<Parse.Object[]>();
const doQueryNear = async function (): Promise<boolean> {
// Check if geolocation API is available in the browser
if ("geolocation" in navigator) {
// Get current location and create the GeoPoint for the query
navigator.geolocation.getCurrentPosition(async function (position) {
const currentPosition: LocationObject = { latitude: position.coords.latitude, longitude: position.coords.longitude };
// Create our GeoPoint
let currentLocationGeoPoint: Parse.GeoPoint = new Parse.GeoPoint(
currentPosition.latitude,
currentPosition.longitude,
);
// Create our query
let parseQuery: Parse.Query = new Parse.Query('City');
// `near` will order results based on distance between the GeoPoint type field from the class and the GeoPoint argument
parseQuery.near('location', currentLocationGeoPoint);
try {
let results: Parse.Object[] = await parseQuery.find();
// Set query results to state variable
setQueryResults(results);
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
alert(`Error! ${error.message}`);
}
}, function (_error: any) {
alert("You need to allow geolocation to retrieve your location on the browser to test this. If you are on an Apple OS, enable it on your system preferences.");
});
} else {
alert("Geolocation services are not supported by your browser.");
}
return false;
};
const doQueryWithinKilometers = async function (): Promise<boolean> {
// Check if geolocation API is available in the browser
if ("geolocation" in navigator) {
// Get current location and create the GeoPoint for the query
navigator.geolocation.getCurrentPosition(async function (position) {
const currentPosition: LocationObject = { latitude: position.coords.latitude, longitude: position.coords.longitude };
// Create our GeoPoint
let currentLocationGeoPoint: Parse.GeoPoint = new Parse.GeoPoint(
currentPosition.latitude,
currentPosition.longitude,
);
// Create our query
let parseQuery: Parse.Query = new Parse.Query('City');
// You can also use `withinMiles` and `withinRadians` the same way,
// but with different measuring unities
parseQuery.withinKilometers('location', currentLocationGeoPoint, 3000);
try {
let results: Parse.Object[] = await parseQuery.find();
// Set query results to state variable
setQueryResults(results);
} catch (error) {
// Error can be caused by lack of Internet connection
alert(`Error! ${error.message}`);
}
}, function (_error: any) {
alert("You need to allow geolocation to retrieve your location on the browser to test this. If you are on an Apple OS, enable it on your system preferences.");
});
} else {
alert("Geolocation services are not supported by your browser.");
}
return false;
};
const doQueryWithinPolygon = async function (): Promise<boolean> {
// Create our GeoPoint polygon points
// In React TypeScript types ('@types/parse'), Parse.Query.withinPolygon expects
// an array of number arrays, so you need to change the declarations
let geoPoint1: number[] = [
-72.42845934415942,
15.822238344514378,
];
let geoPoint2: number[] = [
-97.44765968406668,
-0.7433770196268968,
];
let geoPoint3: number[] = [
-76.52969196322749,
-59.997149373299166,
];
let geoPoint4: number[] = [
-18.346101586021952,
-9.488786415007201,
];
let geoPoint5: number[] = [
-60.00625459569375,
15.414859532811047,
];
// Create our query
let parseQuery: Parse.Query = new Parse.Query('City');
// Note that the polygon is merely an array of GeoPoint objects and that the first and
// last are not connected, so Parse connects them for you
parseQuery.withinPolygon('location', [
geoPoint1,
geoPoint2,
geoPoint3,
geoPoint4,
geoPoint5,
]);
try {
let results: Parse.Object[] = await parseQuery.find();
// Set query results to state variable
setQueryResults(results);
} catch (error) {
// Error can be caused by lack of Internet connection
alert(`Error! ${error}`);
return false;
}
return true;
};
const clearQueryResults = async function (): Promise<boolean> {
setQueryResults(undefined);
return true;
};
return (
<div>
<div className="header">
<img
className="header_logo"
alt="Back4App Logo"
src={
'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png'
}
/>
<p className="header_text_bold">{'React on Back4App'}</p>
<p className="header_text">{'GeoPoint Queries'}</p>
</div>
<div className="container">
<div className="flex_between">
<h2 className="heading">{'Query List'}</h2>
<div className="flex">
<Button
onClick={() => doQueryNear()}
type="primary"
className="heading_button"
color={'#208AEC'}
icon={<SearchOutlined />}
>
QUERY NEAR
</Button>
<Button
onClick={() => doQueryWithinKilometers()}
type="primary"
className="heading_button"
color={'#208AEC'}
icon={<SearchOutlined />}
>
QUERY WITHIN KM
</Button>
<Button
onClick={() => doQueryWithinPolygon()}
type="primary"
className="heading_button"
color={'#208AEC'}
icon={<SearchOutlined />}
>
QUERY WITHIN POLYGON
</Button>
<Button
onClick={() => clearQueryResults()}
type="primary"
className="heading_button"
color={'#208AEC'}
icon={<CloseOutlined />}
>
CLEAR RESULTS
</Button>
</div>
</div>
<Divider />
<div className="flex_between">
<div className="flex_child">
{/* Query list */}
{queryResults !== undefined &&
queryResults.map((result: Parse.Object, index: number) => (
<div className="list_item" key={`${index}`}>
<p className="list_item_title">{`${result.get('name')}`}</p>
</div>
))}
{queryResults !== undefined &&
queryResults.length <= 0 ? (
<p>{'No results here!'}</p>
) : null}
</div>
</div>
</div>
</div>
);
};
Also add these classes to you App.css
file to fully render the component layout:
App.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
html {
box-sizing: border-box;
outline: none;
overflow: auto;
}
*,
*:before,
*:after {
margin: 0;
padding: 0;
box-sizing: inherit;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
font-weight: bold;
}
p {
margin: 0;
}
body {
margin: 0;
background-color: #fff;
}
.container {
width: 100%;
max-width: 900px;
margin: auto;
padding: 20px 0;
text-align: left;
}
.header {
align-items: center;
padding: 25px 0;
background-color: #208AEC;
}
.header_logo {
height: 55px;
margin-bottom: 20px;
object-fit: contain;
}
.header_text_bold {
margin-bottom: 3px;
color: rgba(255, 255, 255, 0.9);
font-size: 16px;
font-weight: bold;
}
.header_text {
color: rgba(255, 255, 255, 0.9);
font-size: 15px;
}
.heading {
font-size: 22px;
}
.flex {
display: flex;
}
.flex_between {
display: flex;
justify-content: space-between;
}
.flex_child {
flex: 0 0 45%;
}
.heading_button {
margin-left: 12px;
}
.list_item {
padding-bottom: 15px;
margin-bottom: 15px;
border-bottom: 1px solid rgba(0,0,0,0.06);
text-align: left;
}
.list_item_title {
color: rgba(0,0,0,0.87);
font-size: 17px;
}
This is how the component should look like after rendering and querying one of the query functions:
Conclusion
At the end of this guide, you learned how GeoPoint data queries work on Parse and how to perform them on Back4App from a React application. In the next guide, you will check how to create and manage users in Parse.