Hello everyone!
Today, I want to talk about the relationship between Electron and React. This is a very important topic, because you usually exchange data between them.
I was using React in Electron Forge, and for my extension, I need to get data from the system and also send a signal to replay the animation.
How to work with data exchange between Electron and React?
So, they exchange data with the help of IPC (Inter-process Communication). They have two types of modules, "ipcMain" and "ipcRender". The first module is used in the main file (index.ts). The second one is used in "preload.ts". To make a connection between them, a channel is used, which you can name whatever you want. This communication can analogous to a Socket, which is also used to channel/event to help exchange data.
How to make it in code?
Well, the first step is to choose a communication pattern. The official site proposes 4 patterns. I consider a pattern called "Main to renderer". Other patterns you can read at this link.
So, let's consider my code in the file "index.ts", which I implemented to send data about MEM, CPU, and Disk.
const sendData = () => {
for (const [key, value] of Object.entries(funcs)) {
mainWindow.webContents.send(key, value());
}
}
const createWindow = (): void => {
mainWindow = new BrowserWindow({
height: 200,
width: 400,
frame: false,
transparent: true,
show: false,
resizable: false,
alwaysOnTop: true,
skipTaskbar: true,
roundedCorners: true,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
},
});
mainWindow.on('blur', () => {
mainWindow?.hide();
});
mainWindow.webContents.on('before-input-event', (event, input) => {
if (input.key === 'Escape') {
mainWindow?.hide();
event.preventDefault();
}
});
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
setInterval(sendData, 1000)
};
app.whenReady().then(() => {
tray = new Tray(icon)
setInterval(() => {
const data = MyNativeAddon.watchTray();
console.log(data);
}, 1000)
tray.on("click", () => {
if (!mainWindow) {
createWindow();
}
if (!position) {
const { x, y } = positioner.calculate(mainWindow.getBounds(), tray.getBounds())
position = { x, y }
}
if (mainWindow?.isVisible()) {
mainWindow.hide();
mainWindow.webContents.send('stop-animation');
} else {
mainWindow.setAlwaysOnTop(true, "pop-up-menu");
mainWindow.setPosition(position.x, position.y + 5);
mainWindow.show();
mainWindow.focus();
mainWindow.webContents.send('start-animation');
}
});
})
I ask you to notice this part of the code:
mainWindow.webContents.send(key, value());
Here, we create a channel on the main side using "ipcMain". Where key is the channel name, after that a value - it's a callback function. The value is not a required parameter.
Then, we need to create the same channel in "preload.ts":
import { contextBridge, ipcRenderer } from "electron";
import funcs from "./objects/funcs.object";
type Callback = (data: any) => void;
const exposedFuncs: Record<
string,
(callback: Callback) => void
> = {};
for (const [key] of Object.entries(funcs)) {
exposedFuncs[key] = (callback: Callback) => {
ipcRenderer.on(key, (_event, data) => callback(data));
};
}
contextBridge.exposeInMainWorld('bridge', {
...exposedFuncs,
startAnimation: (cb: () => void) => {
ipcRenderer.on("start-animation", cb);
},
stopAnimation: (cb: () => void) => {
ipcRenderer.on("stop-animation", cb);
}
});
So, here we use contextBridge, which uses the exposeInMainWorld method and is written with a similar name as in "ipcMain" and similar key and callback.
Also, if you use TypeScript, you must create a <file_name>.d.ts file and write the following code:
export { };
declare global {
interface Window {
bridge: {
getMemory: GetMemory;
getCPU: GetCPU;
getDisk: GetDisk;
};
}
}
How to call methods in React from Electron?
After that, when we create a "bridge", we can call methods in React from Electron:
import { useState, useEffect } from "react";
const useSystem = () => {
const [memory, setMemory] = useState(null);
const [cpu, setCpu] = useState(null);
const [disk, setDisk] = useState(null);
useEffect(() => {
window.bridge.getMemory(data => setMemory(data));
window.bridge.getCPU(data => setCpu(data));
window.bridge.getDisk(data => setDisk(data));
}, [memory, cpu, disk]);
return { memory, cpu, disk };
}
export default useSystem
---------------------------------------------------------------------------------
import { useState, useEffect } from "react"
const useVisible = () => {
const [visible, setVisible] = useState(false)
useEffect(() => {
window.bridge.startAnimation(() => setVisible(true))
window.bridge.stopAnimation(() => setVisible(false))
}, [])
return visible
}
export default useVisible
In my case, I used custom hooks for more comfort. But you can notice that I call functions via window... We can say that we expand the Window API which calls Renderer (our the window..) -> Preload -> Main -> Renderer.
In general, today we considered how to create signals between Electron and React in my example code. I hope this devlog helps other beginner developers.
Thanks to all for your attention! I hope you enjoyed reading my blog!
Have a good day!
Additionally, you can read about it on the official Electron website:
https://www.electronjs.org/docs/latest/api/web-contents
https://www.electronjs.org/docs/latest/api/ipc-renderer
Top comments (0)