Skip to content

Commit

Permalink
fix: Reworked useBattery hook
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich authored Aug 20, 2019
2 parents 49aba72 + ed5f277 commit 1069060
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 41 deletions.
46 changes: 42 additions & 4 deletions docs/useBattery.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,57 @@

React sensor hook that tracks battery status.

>**Note:** current `BatteryManager` API state is obsolete.
>Although it may still work in some browsers, its use is discouraged since it could be removed at any time.

## Usage

```jsx
import {useBattery} from 'react-use';

const Demo = () => {
const state = useBattery();
const batteryState = useBattery();

if (!batteryState.isSupported) {
return (
<div>
<strong>Battery sensor</strong>: <span>not supported</span>
</div>
);
}

if (!batteryState.fetched) {
return (
<div>
<strong>Battery sensor</strong>: <span>supported</span> <br />
<strong>Battery state</strong>: <span>fetching</span>
</div>
);
}

return (
<pre>
{JSON.stringify(state, null, 2)}
</pre>
<div>
<strong>Battery sensor</strong>:&nbsp;&nbsp; <span>supported</span> <br />
<strong>Battery state</strong>: <span>fetched</span> <br />
<strong>Charge level</strong>:&nbsp;&nbsp; <span>{ (batteryState.level * 100).toFixed(0) }%</span> <br />
<strong>Charging</strong>:&nbsp;&nbsp; <span>{ batteryState.charging ? 'yes' : 'no' }</span> <br />
<strong>Charging time</strong>:&nbsp;&nbsp;
<span>{ batteryState.chargingTime ? batteryState.chargingTime : 'finished' }</span> <br />
<strong>Discharging time</strong>:&nbsp;&nbsp; <span>{ batteryState.dischargingTime }</span>
</div>
);
};
```

## Reference

```ts
const {isSupported, level, charging, dischargingTime, chargingTime} = useBattery();
```
- **`isSupported`**_`: boolean`_ - whether browser/devise supports BatteryManager;
- **`fetched`**_`: boolean`_ - whether battery state is fetched;
- **`level`**_`: number`_ - representing the system's battery charge level scaled to a value between 0.0 and 1.0.
- **`charging`**_`: boolean`_ - indicating whether or not the battery is currently being charged.
- **`dischargingTime`**_`: number`_ - remaining time in seconds until the battery is completely discharged and the system will suspend.
- **`chargingTime`**_`: number`_ - remaining time in seconds until the battery is fully charged, or 0 if the battery is already fully charged.
31 changes: 29 additions & 2 deletions src/__stories__/useBattery.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,36 @@ import { useBattery } from '..';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const state = useBattery();
const batteryState = useBattery();

return <pre>{JSON.stringify(state, null, 2)}</pre>;
if (!batteryState.isSupported) {
return (
<div>
<strong>Battery sensor</strong>: <span>not supported</span>
</div>
);
}

if (!batteryState.fetched) {
return (
<div>
<strong>Battery sensor</strong>: <span>supported</span> <br />
<strong>Battery state</strong>: <span>fetching</span>
</div>
);
}

return (
<div>
<strong>Battery sensor</strong>:&nbsp;&nbsp; <span>supported</span> <br />
<strong>Battery state</strong>: <span>fetched</span> <br />
<strong>Charge level</strong>:&nbsp;&nbsp; <span>{(batteryState.level * 100).toFixed(0)}%</span> <br />
<strong>Charging</strong>:&nbsp;&nbsp; <span>{batteryState.charging ? 'yes' : 'no'}</span> <br />
<strong>Charging time</strong>:&nbsp;&nbsp;
<span>{batteryState.chargingTime ? batteryState.chargingTime : 'finished'}</span> <br />
<strong>Discharging time</strong>:&nbsp;&nbsp; <span>{batteryState.dischargingTime}</span>
</div>
);
};

storiesOf('Sensors|useBattery', module)
Expand Down
99 changes: 64 additions & 35 deletions src/useBattery.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,85 @@
import { useEffect, useState } from 'react';
import * as React from 'react';
import * as isEqual from 'react-fast-compare';
import { off, on } from './util';

export interface BatterySensorState {
const { useState, useEffect } = React;

export interface BatteryState {
charging: boolean;
level: number;
chargingTime: number;
dischargingTime: number;
level: number;
}

const useBattery = () => {
const [state, setState] = useState({});
let mounted = true;
let battery: any = null;

const onChange = () => {
const { charging, level, chargingTime, dischargingTime } = battery;
setState({
charging,
level,
chargingTime,
dischargingTime,
});
};
interface BatteryManager extends Readonly<BatteryState>, EventTarget {
onchargingchange: () => void;
onchargingtimechange: () => void;
ondischargingtimechange: () => void;
onlevelchange: () => void;
}

interface NavigatorWithPossibleBattery extends Navigator {
getBattery?: () => Promise<BatteryManager>;
}

type UseBatteryState =
| { isSupported: false } // Battery API is not supported
| { isSupported: true; fetched: false } // battery API supported but not fetched yet
| BatteryState & { isSupported: true; fetched: true }; // battery API supported and fetched

const onBattery = () => {
onChange();
on(battery, 'chargingchange', onChange);
on(battery, 'levelchange', onChange);
on(battery, 'chargingtimechange', onChange);
on(battery, 'dischargingtimechange', onChange);
};
const nav: NavigatorWithPossibleBattery | undefined = typeof navigator === 'object' ? navigator : undefined;
const isBatteryApiSupported = nav && typeof nav.getBattery === 'function';

function useBatteryMock(): UseBatteryState {
return { isSupported: false };
}

function useBattery(): UseBatteryState {
const [state, setState] = useState<UseBatteryState>({ isSupported: true, fetched: false });

useEffect(() => {
(navigator as any).getBattery().then((bat: any) => {
if (mounted) {
battery = bat;
onBattery();
let isMounted = true;
let battery: BatteryManager | null = null;

const handleChange = () => {
if (!isMounted || !battery) {
return;
}
const newState: UseBatteryState = {
isSupported: true,
fetched: true,
level: battery.level,
charging: battery.charging,
dischargingTime: battery.dischargingTime,
chargingTime: battery.chargingTime,
};
!isEqual(state, newState) && setState(newState);
};

nav!.getBattery!().then((bat: BatteryManager) => {
if (!isMounted) {
return;
}
battery = bat;
on(battery, 'chargingchange', handleChange);
on(battery, 'chargingtimechange', handleChange);
on(battery, 'dischargingtimechange', handleChange);
on(battery, 'levelchange', handleChange);
handleChange();
});

return () => {
mounted = false;
isMounted = false;
if (battery) {
off(battery, 'chargingchange', onChange);
off(battery, 'levelchange', onChange);
off(battery, 'chargingtimechange', onChange);
off(battery, 'dischargingtimechange', onChange);
off(battery, 'chargingchange', handleChange);
off(battery, 'chargingtimechange', handleChange);
off(battery, 'dischargingtimechange', handleChange);
off(battery, 'levelchange', handleChange);
}
};
}, []);

return state;
};
}

export default useBattery;
export default isBatteryApiSupported ? useBattery : useBatteryMock;

0 comments on commit 1069060

Please sign in to comment.