diff --git a/docs/useBattery.md b/docs/useBattery.md index 4ddbcc1e..32498d86 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,47 @@ 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 +
+ ); + } + + if (!batteryState.fetched) { + return ( +
+ Battery sensor: supported
+ Battery state: fetching +
+ ); + } return ( -
-      {JSON.stringify(state, null, 2)}
-    
+
+ Battery sensor:   supported
+ Battery state: fetched
+ 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`_ - 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. diff --git a/src/__stories__/useBattery.story.tsx b/src/__stories__/useBattery.story.tsx index 5c6ae635..9a38321a 100644 --- a/src/__stories__/useBattery.story.tsx +++ b/src/__stories__/useBattery.story.tsx @@ -4,9 +4,36 @@ 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 +
+ ); + } + + if (!batteryState.fetched) { + return ( +
+ Battery sensor: supported
+ Battery state: fetching +
+ ); + } + + return ( +
+ Battery sensor:   supported
+ Battery state: fetched
+ 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..2b22b6d1 100644 --- a/src/useBattery.ts +++ b/src/useBattery.ts @@ -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; +interface BatteryManager extends Readonly, EventTarget { + onchargingchange: () => void; + onchargingtimechange: () => void; + ondischargingtimechange: () => void; + onlevelchange: () => void; +} - const onChange = () => { - const { charging, level, chargingTime, dischargingTime } = battery; - setState({ - charging, - level, - chargingTime, - dischargingTime, - }); - }; +interface NavigatorWithPossibleBattery extends Navigator { + getBattery?: () => Promise; +} - const onBattery = () => { - onChange(); - on(battery, 'chargingchange', onChange); - on(battery, 'levelchange', onChange); - on(battery, 'chargingtimechange', onChange); - on(battery, 'dischargingtimechange', onChange); - }; +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 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({ 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;