Skip to content

Commit

Permalink
feat: rewrite from HOC to hook, support React 18
Browse files Browse the repository at this point in the history
Rewrite the component from HOC to hook. This makes the package much
easier to use. Also rewrite the code to TypeScript, improve the tests
and add support for React 18.

BREAKING CHANGE: Changed the component from HOC to hook. The TypeScript types are now generated from
code so there might be some slight changes. We now require React >= 16.8.0 since we need hook support.

Fixes #918
  • Loading branch information
no23reason committed May 28, 2022
1 parent aa41c1e commit ac0bc6b
Show file tree
Hide file tree
Showing 23 changed files with 1,364 additions and 2,208 deletions.
9 changes: 0 additions & 9 deletions .babelrc

This file was deleted.

21 changes: 5 additions & 16 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended"
],
"parser": "@babel/eslint-parser",
"env": {
"browser": true,
"node": true,
Expand All @@ -18,18 +20,5 @@
"version": "detect"
}
},
"ignorePatterns": "demo",
"rules": {
"comma-dangle": [1, "always-multiline"],
"jsx-quotes": 1,
"no-console": 1,
"quotes": [2, "double"],
"react/jsx-boolean-value": 1,
"react/jsx-sort-props": 1,
"react/jsx-wrap-multilines": 1,
"react/no-did-mount-set-state": 1,
"react/no-did-update-set-state": 1,
"react/no-multi-comp": 1,
"react/self-closing-comp": 1
}
"ignorePatterns": "demo"
}
3 changes: 1 addition & 2 deletions .postcssrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"plugins": {
"tailwindcss": {},
"autoprefixer": {}
"tailwindcss": {}
}
}
180 changes: 65 additions & 115 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
![Node.js CI](https://github.com/no23reason/react-geolocated/workflows/Node.js%20CI/badge.svg) [![npm version](https://img.shields.io/npm/v/react-geolocated.svg)](https://www.npmjs.com/package/react-geolocated) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)

# react-geolocated - React.js Higher-Order Component for using [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation)
# react-geolocated - React hook for using [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation)

## Demo

Basic demo can be found at the [demo page](https://no23reason.github.io/react-geolocated/).

## HOC version

This package used to be a HOC, not a hook. If you want to use the HOC version, please stick with versions < 4.

## Basic Usage

Install using `npm`:
Expand All @@ -16,58 +20,59 @@ npm install react-geolocated --save

Then use in your application like this:

```js
```jsx
import React from "react";
import { geolocated } from "react-geolocated";

class Demo extends React.Component {
render() {
return !this.props.isGeolocationAvailable ? (
<div>Your browser does not support Geolocation</div>
) : !this.props.isGeolocationEnabled ? (
<div>Geolocation is not enabled</div>
) : this.props.coords ? (
<table>
<tbody>
<tr>
<td>latitude</td>
<td>{this.props.coords.latitude}</td>
</tr>
<tr>
<td>longitude</td>
<td>{this.props.coords.longitude}</td>
</tr>
<tr>
<td>altitude</td>
<td>{this.props.coords.altitude}</td>
</tr>
<tr>
<td>heading</td>
<td>{this.props.coords.heading}</td>
</tr>
<tr>
<td>speed</td>
<td>{this.props.coords.speed}</td>
</tr>
</tbody>
</table>
) : (
<div>Getting the location data&hellip; </div>
);
}
}

export default geolocated({
positionOptions: {
enableHighAccuracy: false,
},
userDecisionTimeout: 5000,
})(Demo);
import { useGeolocated } from "react-geolocated";

const Demo = () => {
const { ccords, isGeolocationAvailable, isGeolocationEnabled } =
useGeolocated({
positionOptions: {
enableHighAccuracy: false,
},
userDecisionTimeout: 5000,
});

return !isGeolocationAvailable ? (
<div>Your browser does not support Geolocation</div>
) : !isGeolocationEnabled ? (
<div>Geolocation is not enabled</div>
) : coords ? (
<table>
<tbody>
<tr>
<td>latitude</td>
<td>{coords.latitude}</td>
</tr>
<tr>
<td>longitude</td>
<td>{coords.longitude}</td>
</tr>
<tr>
<td>altitude</td>
<td>{coords.altitude}</td>
</tr>
<tr>
<td>heading</td>
<td>{coords.heading}</td>
</tr>
<tr>
<td>speed</td>
<td>{coords.speed}</td>
</tr>
</tbody>
</table>
) : (
<div>Getting the location data&hellip; </div>
);
};

export default Demo;
```

## Props
## Hook return value

The props passed to the wrapped component are:
The values returned from the hook are:

```js
{
Expand All @@ -84,45 +89,15 @@ The props passed to the wrapped component are:
isGeolocationAvailable, // boolean flag indicating that the browser supports the Geolocation API
isGeolocationEnabled, // boolean flag indicating that the user has allowed the use of the Geolocation API
positionError, // object with the error returned from the Geolocation API call
getPosition, // a callback you can use to trigger the location query manually
}
```

The `coords` prop is equivalent to the [Coordinates](https://developer.mozilla.org/en-US/docs/Web/API/Coordinates) object and the `positionError` is equivalent to the [PositionError](https://developer.mozilla.org/en-US/docs/Web/API/PositionError).

Additional props the resulting component can take:

```js
{
// callback call on Geolocation API error, takes PositionError as the only argument
onError,
// callback call on Geolocation API success, takes Position as the only argument
onSuccess,
}
```

### PropTypes

Unfortunately, the `geolocated` HOC cannot add the prop types to the wrapped component directly, as the ESLint will not pick that up. For this reason, prop types are exported as the `geoPropTypes` object. Using them is simple with [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) (or if you already depend on it, lodash [`merge`](https://lodash.com/docs#merge) function is useful as well), or, if your environment supports it, using the [object spread syntax](https://developer.mozilla.org/cs/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment):

```js
import React from "react";
import { geolocated, geoPropTypes } from "react-geolocated";

class Demo extends React.Component {
// Same as the basic example
}

// Using Object.assign
Demo.propTypes = Object.assign({}, Demo.propTypes, geoPropTypes);
// Using ES6 object spread syntax
Demo.propTypes = { ...Demo.propTypes, ...geoPropTypes };

export default geolocated()(Demo);
```
The `coords` value is equivalent to the [Coordinates](https://developer.mozilla.org/en-US/docs/Web/API/Coordinates) object and the `positionError` is equivalent to the [PositionError](https://developer.mozilla.org/en-US/docs/Web/API/PositionError).

## Configuration

The `geolocated` function takes optional configuration parameter:
The `useGeolocated` hook takes optional configuration parameter:

```js
{
Expand All @@ -135,55 +110,30 @@ The `geolocated` function takes optional configuration parameter:
userDecisionTimeout: null,
suppressLocationOnMount: false,
geolocationProvider: navigator.geolocation,
isOptimisticGeolocationEnabled: true
isOptimisticGeolocationEnabled: true,
onError,
onSuccess,
}
```

The `positionOptions` object corresponds to the [PositionOptions](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) of the Geolocation API.

By default the component only sets position once. To watch the user's position and provide live updates to position, set `watchPosition = true`. The geolocation event handler is unregistered when the component unmounts.

If set, the `userDecisionTimeout` determines how much time (in miliseconds) we give the user to make the decision whether to allow to share their location or not. In Firefox, if the user declines to use their location, the Geolocation API call does not end with an error. Therefore we want to fallback to the error state if the user declines and the API does not tell us.
If set, the `userDecisionTimeout` determines how much time (in milliseconds) we give the user to make the decision whether to allow to share their location or not. In Firefox, if the user declines to use their location, the Geolocation API call does not end with an error. Therefore we want to fallback to the error state if the user declines and the API does not tell us.

The location is obtained when the component mounts by default. If you want to prevent this and get the location later, set the `suppressLocationOnMount` to `true` and using a `ref` in the parent component call its `getLocation` method (see the demo's [`App` component](https://github.com/no23reason/react-geolocated/blob/dcbe587880751519a6ac6adaa6c49780b609e3c2/demo/App.jsx#L14-L21) for example of this).
The location is obtained when the component mounts by default. If you want to prevent this and get the location later, set the `suppressLocationOnMount` to `true` and use the `getLocation` function returned by the hook to trigger the geolocation query manually.

The `geolocationProvider` allows to specify alternative source of the geolocation API. This was added mainly for testing purposes, however feel free to use it if need be.

The `isOptimisticGeolocationEnabled` allows you to set the default value of `isGeolocationEnabled`. By default it is `true`, which means `isGeolocationEnabled` will be `true` on first render. There may be cases where you don't want to assume that the user will give permission, ie you want the first value to for `isGeolocationEnabled` to be `false`. In that case, you can set `isOptimisticGeolocationEnabled` to `false`.

## TypeScript

This project ships with type definitions for TypeScript provided. You can use them in your TypeScript files like this:

```js
import * as React from "react";
import { GeolocatedProps, geolocated } from "react-geolocated";

interface IDemoProps {
label: string;
}

class Demo extends React.Component<IDemoProps & GeolocatedProps> {
render(): JSX.Element {
return (
<div>
label: {this.props.label}
latitude: {this.props.coords && this.props.coords.latitude}
</div>
);
}
}

export default geolocated()(Demo);
```
The `onError` callback is called when the geolocation query fails or when the time for the user decision passes.
The `onSuccess` is called when the geolocation query succeeds.

## Browser support

- Chrome ≥ 5
- Firefox ≥ 3.5
- Internet Explorer ≥ 9
- Opera ≥ 10.60
- Safari ≥ 5
The package supports all the browsers with ES6 support (i.e. any modern browser). If you need to support IE11, stick to version < 4 of this package.

## Acknowledgements

Expand Down
File renamed without changes.
60 changes: 0 additions & 60 deletions demo/Demo.jsx

This file was deleted.

0 comments on commit ac0bc6b

Please sign in to comment.