useBattery
React hook that tracks battery state
Example
import clsx from "clsx";
import useBattery, { type BatteryState } from "@/hooks/useBattery";
export default function UseBatteryExample() {
const { isSupported, ...batteryState } = useBattery();
if (!isSupported) {
return <p>Battery API is not supported</p>;
}
const { fetched } = batteryState as { fetched: boolean };
if (!fetched) {
return <p>Battery API is not fetched yet</p>;
}
const { charging, level, chargingTime, dischargingTime } =
batteryState as BatteryState;
const getLevelColor = () => {
if (level < 0.25) {
return "bg-ctp-red";
} else if (level < 0.5) {
return "bg-ctp-yellow";
} else {
return "bg-ctp-green";
}
};
const getBorderColor = () => {
if (charging) {
return "border-ctp-yellow";
} else {
return "border-ctp-surface2";
}
};
return (
<div className="flex items-center justify-start">
<div className="w-48">
<div className="relative my-1 flex w-1/2 rounded border-2 border-ctp-surface2 shadow">
<div className="absolute z-10 ml-24 mt-2 flex h-6 rounded-r border-r-8 border-ctp-surface2"></div>
<div
className={clsx(
"m-1 flex cursor-default items-center justify-center py-4 text-center text-xs font-bold leading-none",
getLevelColor(),
)}
style={{ width: `${level * 100}%` }}
>
<div className="absolute left-0 mx-8 text-ctp-surface2">
{level * 100}%
</div>
</div>
</div>
</div>
<div className="text-ctp-text">
Is charging: {charging ? "Yes" : "No"}
<br />
Charging time: {chargingTime}
<br />
Discharging time: {dischargingTime}
</div>
</div>
);
}
Code
import { useEffect, useState } from "react";
export interface BatteryState {
charging: boolean;
chargingTime: number;
dischargingTime: number;
level: number;
}
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 nav: NavigatorWithPossibleBattery | undefined =
typeof navigator !== "undefined" ? navigator : undefined;
const isBatteryApiSupported = nav && typeof nav.getBattery === "function";
function useBatteryMock(): UseBatteryState {
return { isSupported: false };
}
/**
* React hook that tracks battery state
* @returns {UseBatteryState} battery state
*/
function useBattery(): UseBatteryState {
const [state, setState] = useState<UseBatteryState>({
isSupported: true,
fetched: false,
});
useEffect(() => {
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,
};
setState(newState);
};
nav!.getBattery!().then((bat: BatteryManager) => {
if (!isMounted) {
return;
}
battery = bat;
window.addEventListener("chargingchange", handleChange);
window.addEventListener("chargingtimechange", handleChange);
window.addEventListener("dischargingtimechange", handleChange);
window.addEventListener("levelchange", handleChange);
handleChange();
});
return () => {
isMounted = false;
if (battery) {
window.removeEventListener("chargingchange", handleChange);
window.removeEventListener("chargingtimechange", handleChange);
window.removeEventListener("dischargingtimechange", handleChange);
window.removeEventListener("levelchange", handleChange);
}
};
}, []);
return state;
}
export default isBatteryApiSupported ? useBattery : useBatteryMock;