From 310fb6dc55cab56b93a6d112c4ec52bf74804e7a Mon Sep 17 00:00:00 2001 From: xobotyi Date: Mon, 19 Aug 2019 11:09:59 +0300 Subject: [PATCH] Reworked useBattery hook; Fix: #308; --- docs/useBattery.md | 33 ++++++- src/__stories__/useBattery.story.tsx | 24 ++++- src/useBattery.ts | 142 ++++++++++++++++++--------- 3 files changed, 147 insertions(+), 52 deletions(-) diff --git a/docs/useBattery.md b/docs/useBattery.md index 4ddbcc1e..69f0403c 100644 --- a/docs/useBattery.md +++ b/docs/useBattery.md @@ -2,6 +2,9 @@ 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 @@ -9,12 +12,34 @@ React sensor hook that tracks battery status. import {useBattery} from 'react-use'; const Demo = () => { - const state = useBattery(); + const batteryState = useBattery(); + if (!batteryState.isSupported) { + return ( +
+ Battery sensor:
not supported
+
+ ); + } return ( -
-      {JSON.stringify(state, null, 2)}
-    
+
+ Battery sensor:  supported
+ Charge level:  {(batteryState.level * 100).toFixed(0)}%
+ Charging:  {batteryState.charging ? 'yes' : 'no'}
+ Charging time:  {batteryState.chargingTime ? batteryState.chargingTime : 'finished'}
+ Discharging time:  {batteryState.dischargingTime} +
); }; ``` + +## Reference + +```ts +const {isSupported, level, charging, dischargingTime, chargingTime} = useBattery(); +``` +- **`isSupported`**_`: boolean`_ - wheter browser/devise supports BatteryManager; +- **`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. diff --git a/src/__stories__/useBattery.story.tsx b/src/__stories__/useBattery.story.tsx index 5c6ae635..922b1442 100644 --- a/src/__stories__/useBattery.story.tsx +++ b/src/__stories__/useBattery.story.tsx @@ -4,9 +4,29 @@ import { useBattery } from '..'; import ShowDocs from './util/ShowDocs'; const Demo = () => { - const state = useBattery(); + const batteryState = useBattery(); - return
{JSON.stringify(state, null, 2)}
; + if (!batteryState.isSupported) { + return ( +
+ Battery sensor:
not supported
+
+ ); + } + return ( +
+ Battery sensor:  supported +
+ Charge level:  {(batteryState.level * 100).toFixed(0)}% +
+ Charging:  {batteryState.charging ? 'yes' : 'no'} +
+ Charging time:   + {batteryState.chargingTime ? batteryState.chargingTime : 'finished'} +
+ Discharging time:  {batteryState.dischargingTime} +
+ ); }; storiesOf('Sensors|useBattery', module) diff --git a/src/useBattery.ts b/src/useBattery.ts index a5cfd5c2..fdba50ca 100644 --- a/src/useBattery.ts +++ b/src/useBattery.ts @@ -1,56 +1,106 @@ -import { useEffect, useState } from 'react'; +import * as React from 'react'; import { off, on } from './util'; -export interface BatterySensorState { - charging: boolean; - level: number; - chargingTime: number; - dischargingTime: number; +enum BatteryManagerEvents { + levelChange = 'levelchange', + dischargingTimeChange = 'dischargingtimechange', + chargingTimeChange = 'chargingtimechange', + chargingChange = 'chargingchange', } -const useBattery = () => { - const [state, setState] = useState({}); - let mounted = true; - let battery: any = null; +export interface BatteryState { + charging: boolean; + chargingTime: number; + dischargingTime: number; + level: number; +} - const onChange = () => { - const { charging, level, chargingTime, dischargingTime } = battery; - setState({ - charging, - level, - chargingTime, - dischargingTime, - }); - }; +interface BatteryManager extends Readonly, EventTarget { + onchargingchange: () => void; + onchargingtimechange: () => void; + ondischargingtimechange: () => void; + onlevelchange: () => void; +} - const onBattery = () => { - onChange(); - on(battery, 'chargingchange', onChange); - on(battery, 'levelchange', onChange); - on(battery, 'chargingtimechange', onChange); - on(battery, 'dischargingtimechange', onChange); - }; +interface NavigatorWithPossibleBattery extends Navigator { + getBattery?: () => Promise; +} - useEffect(() => { - (navigator as any).getBattery().then((bat: any) => { - if (mounted) { - battery = bat; - onBattery(); - } - }); +const nav: NavigatorWithPossibleBattery = navigator; - return () => { - mounted = false; - if (battery) { - off(battery, 'chargingchange', onChange); - off(battery, 'levelchange', onChange); - off(battery, 'chargingtimechange', onChange); - off(battery, 'dischargingtimechange', onChange); - } - }; - }, []); - - return state; +type UseBatteryState = BatteryState & { + isSupported: boolean; }; -export default useBattery; +export default function useBattery() { + const [state, setState] = React.useState({ + isSupported: nav && typeof nav.getBattery !== 'undefined', + level: 1, + charging: true, + dischargingTime: Infinity, + chargingTime: 0, + }); + const battery = React.useRef(); + let isMounted = true; + + if (state.isSupported) { + const onChange = React.useCallback(() => { + if (isMounted && battery.current) { + setState({ + isSupported: true, + level: battery.current.level, + charging: battery.current.charging, + dischargingTime: battery.current.dischargingTime, + chargingTime: battery.current.chargingTime, + }); + } + }, [setState]); + + const bindBatteryEvents = React.useCallback( + (bat: BatteryManager) => { + on(bat, BatteryManagerEvents.chargingChange, onChange); + on(bat, BatteryManagerEvents.chargingTimeChange, onChange); + on(bat, BatteryManagerEvents.dischargingTimeChange, onChange); + on(bat, BatteryManagerEvents.levelChange, onChange); + }, + [onChange] + ); + + const unbindBatteryEvents = React.useCallback( + (bat: BatteryManager) => { + off(bat, BatteryManagerEvents.chargingChange, onChange); + off(bat, BatteryManagerEvents.chargingTimeChange, onChange); + off(bat, BatteryManagerEvents.dischargingTimeChange, onChange); + off(bat, BatteryManagerEvents.levelChange, onChange); + }, + [onChange] + ); + + React.useEffect(() => { + battery.current && bindBatteryEvents(battery.current); + + return () => { + battery.current && unbindBatteryEvents(battery.current); + }; + }, [onChange]); + + React.useEffect(() => { + nav.getBattery!().then((bat: BatteryManager) => { + if (!isMounted) { + return; + } + + battery.current = bat; + bindBatteryEvents(bat); + }); + + return () => { + isMounted = false; + + battery.current && unbindBatteryEvents(battery.current); + }; + }, []); + } + + return state; +}