Data Flow
Lets get some terms squared away:
Backend - the nodejs process that is running in the background
Frontend - the Html/Javascript/React Window
Context Bridge - the stitching between the Frontend and the Backend
At times events in the Frontend may inform the Backend, and others vice-versa.
Sending Frontend Messages To Backend
This is relatively straightforward. Electron has defined something called an IpcRenderer object which listens on a channel, and provides APIs to the frontend that will invoke the backend.
In the preload.js you will define some API that the frontend will Call, and then call that API on the backend.
So there are three components to a Frontend to Backend component:
Create the API on the backend (main.ts)
Create the Context Bridge Code (preload.js)
Calling the Context Bridge Code on the Frontend
Note: Depending on the code you write in item number 2, you can expect a response, or just hand off some event to the Backend. (You can also add arguments to be passed to the Backend).
In our example we want some configuration sent from the Backend to the Frontend on a settings page.
Firstly, lets define some code to handle the “get-config” event on the Backend.
// main.ts
import { ipcMain } from 'electron';
ipcMain.handle('get-config', async () => {
logger.info('IPC: Get Config');
return this.configService.loadConfig();
});Next, lets ensure that the code we need to define for our stitching code
// preload.js
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
const electronHandler = {
ipcRenderer: {
getConfig: () => ipcRenderer.invoke('get-config'),
}
};
contextBridge.exposeInMainWorld('electron', electronHandler);
export type ElectronHandler = typeof electronHandler;
Please note that we expose this stitching code as the variable ‘electron’.
And finally we have the Frontend code that calls that Context Bridge.
import React, { useEffect, useState } from 'react';
import Config from '../main/service/config/config';
const App: React.FC = () => {
const [config, setConfig] = useState<Config | null>(null);
useEffect(() => {
const fetchConfig = async () => {
try {
const configData = await window.electron.ipcRenderer.getConfig();
setConfig(configData);
} catch (error) {
console.error('Error fetching config:', error);
// Optionally, handle the error case (e.g., set an error state)
}
};
fetchConfig();
}, []);
}Notice that the call is asynchronous, meaning React will render perhaps before the call completes.
To sum up the roles of our components, we have:
Frontend, the component that can interact with the user
Context Bridge, the stitching code between the Frontend and the Backend
Backend, the privileged component that can interact with the system
You can read more about the Context Bridge on Electron’s site: https://www.electronjs.org/docs/latest/api/ipc-renderer
Thanks,
Ben