React

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:

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:

React Back4App

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:

React Back4App

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.