Build lightweight and fast battery monitor web app using SolidJS

Harsh Mangalam - Mar 30 '22 - - Dev Community

Battery api provide an easy way to handle battery status in browser.

Battery api can provide you following information:-

  • Battery charging level between 0.00 to 1.00 in decimal number. You can easily convert this decimal number into percentage by multiplying with 100.

  • Charging or Discharging boolean status

  • Charging time in seconds

  • Discharging time in seconds

SolidJS

Solid is a declarative JavaScript library for creating user interfaces. It does not use a Virtual DOM. Instead it opts to compile its templates down to real DOM nodes and wrap updates in fine grained reactions. This way when your state updates only the code that depends on it runs.
SolidJS Documentation

Init new Solidjs project from Javascript template



 > npx degit solidjs/templates/js my-app
 > cd my-app
 > pnpm i
 > pnpm run dev



Enter fullscreen mode Exit fullscreen mode

Install Hope UI



pnpm add @hope-ui/solid @stitches/core solid-transition-group



Enter fullscreen mode Exit fullscreen mode

Open the project in vscode



code .



Enter fullscreen mode Exit fullscreen mode

Lets start code by building a useBattery hook . This hook will call web battery api and listen for battery event and update the store state



> mkdir hooks
> cd hooks
> touch useBattery.jsx



Enter fullscreen mode Exit fullscreen mode

useBattery.jsx



import { onMount } from "solid-js";
import { createStore } from "solid-js/store";
export default function useBattery() {
  const [store, setStore] = createStore({
    isSupported: true,
    charging: false,
    chargingTime: 0,
    dischargingTime: 0,
    level: 0,
  });
  onMount(async () => {
    try {
      const battery = await navigator.getBattery();

      console.log(battery);
      updateAllBatteryInfo(battery);

      battery.addEventListener("chargingchange", () => {
        updateChargeInfo(battery);
      });
      battery.addEventListener("levelchange", () => {
        updateLevelInfo(battery);
      });
      battery.addEventListener("chargingtimechange", () => {
        updateChargingInfo(battery);
      });
      battery.addEventListener("dischargingtimechange", () => {
        updateDischargingInfo(battery);
      });
    } catch (error) {
      console.log(error);
      setStore("isSupported", false);
    }
  });

  function updateAllBatteryInfo(battery) {
    updateChargeInfo(battery);
    updateLevelInfo(battery);
    updateChargingInfo(battery);
    updateDischargingInfo(battery);
  }

  function updateChargeInfo(battery) {
    setStore("charging", battery.charging);
  }

  function updateLevelInfo(battery) {
    setStore("level", battery.level);
  }

  function updateChargingInfo(battery) {
    setStore("chargingTime", battery.chargingTime);
  }

  function updateDischargingInfo(battery) {
    setStore("dischargingTime", battery.dischargingTime);
  }

  return {
    store,
  };
}




Enter fullscreen mode Exit fullscreen mode

onMount is a lifecycle function in solidjs which run only once when component mount you can run side effect inside this function like api call. We use this function to add event listener for battery event and initialise store with initial value provided by battery api.

The createStore call takes the initial value (here we provide battery related fields) and returns a read/write tuple. The first element is the store proxy which is readonly, and the second is a setter function.

navigator.getBattery() call web battery api and return promise this resolves to BatteryManager Object contains information about charging level, charging time etc...

If browser does not support battery web api then catch block will execute and it update isSupported to false and on ui screen we can show alert box.

BatteryManager Object during discharging



{
  charging: false
  chargingTime: Infinity
  dischargingTime: 13684
  level: 0.62
}


Enter fullscreen mode Exit fullscreen mode

chargingchange event fired when you connect or disconnect your device from charger.

levelchange event fired when your device battery level change. Suppose your device is charging and their level change from 0.79 to 0.80 then this event will fired.

chargingtimechange event fired when battery charging time will updated

dischargingtimechange fired when battery discharging time will updated.

Lets build the ui to show the battery related data in App.js




import {
  Alert,
  AlertDescription,
  AlertIcon,
  AlertTitle,
  Box,
  CircularProgress,
  CircularProgressIndicator,
  CircularProgressLabel,
  Container,
  GridItem,
  Heading,
  HopeProvider,
  HStack,
  SimpleGrid,
  Tag,
  Text,
  VStack,
} from "@hope-ui/solid";
import { Show } from "solid-js";
import ThemeSwitcher from "./components/ThemeSwitcher";
import useBattery from "./hooks/useBattery";

export default function App() {
  const { store } = useBattery();
  return (
    <HopeProvider config={{ initialColorMode: "dark" }}>
      <Box minH={"100vh"} h="$full" w={"$full"} py="$10">
        <VStack spacing={"$3"}>
          <Heading textAlign={"center"} fontSize={"$6xl"}>
            Battery &nbsp;
            <Box as="span" color={"$primary10"}>
              Monitor
            </Box>
          </Heading>

          <HStack spacing={"$2"}>
            <Show when={store.isSupported}>
              <Tag
                colorScheme={store.charging ? "success" : "danger"}
                size={"lg"}
                variant="dot"
                dotPlacement="start"
              >
                <Show when={store.charging} fallback="Discharging">
                  Charging
                </Show>
              </Tag>
            </Show>
            <ThemeSwitcher />
          </HStack>
        </VStack>
        <Container mt={"$10"}>
          <Show
            when={store.isSupported}
            fallback={
              <Alert
                status="danger"
                variant="subtle"
                flexDirection="column"
                justifyContent="center"
                textAlign="center"
                height="200px"
              >
                <AlertIcon boxSize="40px" mr="0" />
                <AlertTitle mt="$4" mb="$1" fontSize="$lg">
                  Unsupported Browser
                </AlertTitle>
                <AlertDescription maxWidth="$sm">
                  Your browser does not support Web Battery API
                </AlertDescription>
              </Alert>
            }
          >
            <SimpleGrid
              w={"$full"}
              columns={{ "@initial": 1, "@sm": 2, "@md": 3 }}
              justifyContent="center"
            >
              <GridItem mx={"auto"}>
                <CircularProgress thickness={"$0_5"} size={"$xs"} value={100}>
                  <CircularProgressIndicator color={"$warning10"} />
                  <CircularProgressLabel>
                    <VStack spacing={"$6"}>
                      <Heading fontSize={"$xl"}> Charging Time</Heading>
                      <Text fontSize={"$xl"}>
                        {Math.round(store.chargingTime / 60)} Minutes
                      </Text>
                    </VStack>
                  </CircularProgressLabel>
                </CircularProgress>
              </GridItem>
              <GridItem mx={"auto"}>
                <CircularProgress size={"$xs"} value={store.level * 100}>
                  <CircularProgressIndicator color={"$success10"} />
                  <CircularProgressLabel>
                    <VStack spacing={"$6"}>
                      <Heading fontSize={"$xl"}> Battery Level</Heading>
                      <Text fontSize={"$xl"}>{store.level * 100} %</Text>
                    </VStack>
                  </CircularProgressLabel>
                </CircularProgress>
              </GridItem>

              <GridItem mx={"auto"}>
                <CircularProgress thickness={"$0_5"} size={"$xs"} value={100}>
                  <CircularProgressIndicator color={"$primary10"} />
                  <CircularProgressLabel>
                    <VStack spacing={"$6"}>
                      <Heading fontSize={"$xl"}> Discharging Time</Heading>
                      <Text fontSize={"$xl"}>
                        {Math.round(store.dischargingTime / 60)} Minutes
                      </Text>
                    </VStack>
                  </CircularProgressLabel>
                </CircularProgress>
              </GridItem>
            </SimpleGrid>
          </Show>
        </Container>
      </Box>
    </HopeProvider>
  );
}




Enter fullscreen mode Exit fullscreen mode

ThemeSwitcher.jsx component handle switching between dark and light mode.





import { Button, useColorMode } from "@hope-ui/solid";

export default function ThemeSwitcher() {
  const { colorMode, toggleColorMode } = useColorMode();

  return (
    <Button
      rounded={"$full"}
      size="sm"
      colorScheme={"warning"}
      onClick={toggleColorMode}
    >
      {colorMode() === "light" ? "Light" : "Dark"} mode
    </Button>
  );
}




Enter fullscreen mode Exit fullscreen mode

Discharging State

Charging Status

GitHub logo harshmangalam / solidjs-web-battery-monitor

Web battery monitor using Solidjs

Usage

Those templates dependencies are maintained via pnpm via pnpm up -Lri.

This is the reason you see a pnpm-lock.yaml. That being said, any package manager will work. This file can be safely be removed once you clone a template.

$ npm install # or pnpm install or yarn install
Enter fullscreen mode Exit fullscreen mode

Learn more on the Solid Website and come chat with us on our Discord

Available Scripts

In the project directory, you can run:

npm dev or npm start

Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.

The page will reload if you make edits.

npm run build

Builds the app for production to the dist folder.
It correctly bundles Solid in production mode and optimizes the build for the best performance.

The build is minified and the filenames include the hashes.
Your app is ready to be deployed!

Deployment

You can…

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .