← All posts
Daniel Strong Tuesday, November 1, 2022

Custom Titlebar for an Electron app with React (Part 2)

Share

AUTHOR

Daniel Strong, Frontend Engineer

Daniel is a Frontend Engineer at Codiga.

He is a passionate frontend engineer, teacher, and learner. He has worked on or led several creative projects where he's grown his leadership, management, design, and programming skills.

See all articles

Note: this is part 2 of "Custom Titlebar for an Electron app with React". We suggest reading part 1 first.

In part 1, we got started with styling buttons to look similar to their native counterparts and ultimately, got them connected with our main Electron process to be functional.

We're not going to go any further with styling the titlebar here, instead, we'll switch to some other features that you can add to your titlebar or app to give it a fantastic UX.

Detect the current OS build

Something we didn't touch on in the first post was how can you tell which platform you're on. How do you know if you should show your Windows and macOS traffic lights?

Inside your preload.ts file, we'll detect if the platform is darwin or not.

contextBridge.exposeInMainWorld("electron", {
  isMac: process.platform === "darwin",
});

Now to stop those pesky Typescript errors before they happen, put the following in your preload.d.ts file.

declare global {
  interface Window {
    electron: {
      isMac: boolean; // this line right here
      ipcRenderer: {
        sendMessage(channel: Channels, args: unknown[]): void;
        on(
          channel: string,
          func: (...args: unknown[]) => void
        ): (() => void) | undefined;
        once(channel: string, func: (...args: unknown[]) => void): void;
      };
    };
  }
}

Now wherever you are rendering your Windows or macOS titlebar buttons, check your platform, and if it's different return null.

// Windows
if (!window.electron?.isMac) return null;
// macOS
if (window.electron?.isMac) return null;

Detect a network connection change

Spotify, a prominent music streaming app built with Electron, has a nice feature that we'll implement.

When you're logged in:

Spotify with a connection

When you have no internet connection:

Spotify with no connection

So how can detect when the user has lost their internet connection while on our app?

We'll need to store the current online status somewhere. This will be the value that we read from to update the UI.

const [isOnline, setIsOnline] = useState(true);

If you want to only show something within your titlebar, you can place this logic there. If you want to display this globally, you could use React Context and use the state wherever it's needed. In our app, we wanted to show it in multiple spots, so we used React Context.

Now we need a way to update our state. We use Navigator.onLine to read the current network status and attach online and offline event listeners, which will update our state whenever a change in the network state happens.

useEffect(() => {
  const updateOnlineStatus = () => {
    const currentStatus = navigator.onLine;
    if (isOnline && !currentStatus) setIsOnline(false);
    if (!isOnline && currentStatus) setIsOnline(true);
  };

  window.addEventListener("online", updateOnlineStatus);
  window.addEventListener("offline", updateOnlineStatus);

  return () => {
    window.removeEventListener("online", updateOnlineStatus);
    window.removeEventListener("offline", updateOnlineStatus);
  };
}, [isOnline, setIsOnline]);

Custom about/info/help section

Slack, another prominent app built with Electron, has our second targeted feature.

When building out an app, it can be difficult to place important information that can be reached quickly by new or experienced users.

Next to your user image, there's a help button.

Slack user closed menu

When you click on the button, Slack opens a sidebar with tons of information and direct help links.

Slack user opened menu

Your app might not have that many outbound links and in that case, you would be more like us. We wanted to have this simple functionality with room to expand in the future. Ours currently is more of an about section, but there's plenty of room for growth.

Codiga app about

Grabbing the version number dynamically

You might have noticed in the image above that we have the version number listed. We don't hard code that and update it each time. Instead, we grab the version number from our main Electron process.

Just like we did while detecting network states, decide if you want the version number available globally or in just one place.

// create some default state value
const [appVersion, setAppVersion] = useState("0.0.0");

// this will only run once (when the component has mounted)
useEffect(() => {
  // we listen for an 'app-version' message from our ipcRenderer
  window.electron?.ipcRenderer.once("app-version", (arg) => {
    setAppVersion(arg as string);
  });
  // we send a message to our ipcRenderer
  window.electron?.ipcRenderer.sendMessage("app-version", [""]);
}, []);

Refer to part 1, if you need a refresher on ipcRenderer.

Imagine we are calling our friend ipcRenderer and asking what the current version is (sendMessage) and waiting to hear their response (on). Meanwhile our friend, ipcRenderer, needs to look up what the current version is and tell us.

For the latter part, we'll need to update our main.ts to include the following:

ipcMain.on("app-version", async (event) => {
  event.reply("app-version", app.getVersion());
});

You'll also need to update your preload.ts file to include the following:

export type Channels = "app-version";

contextBridge.exposeInMainWorld("electron", {
  ipcRenderer: {
    sendMessage(channel: Channels, args: unknown[]) {
      ipcRenderer.send(channel, args);
    },
    on(channel: Channels, func: (...args: unknown[]) => void) {
      const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>
        func(...args);
      ipcRenderer.on(channel, subscription);
      return () => ipcRenderer.removeListener(channel, subscription);
    },
  },
});

Now your app can get the current app version.

Conclusion

That's it for how to build a custom titlebar for Electron in a React app.

You should have the knowledge to go out and build any custom features you can think of. Anything from a search input, back and forward buttons, toggle view buttons, or something else.

Related Electron Posts

Schedule a demo

Code analyzed in seconds with Codiga Automated Code Reviews.

Write code faster with the Codiga Coding Assistant.

Let's talk!