npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

electron-pronto-interconnect

v1.2.0

Published

Instant React State Hooks for Electron

Readme

Electron Pronto Interconnect

Static Badge NPM Downloads npm version License

Instant, synchronized React States for your Electron applications. Bridge the gap between your main process and renderer process with minimal setup and reactive state management.

Why Electron Pronto Interconnect?

Managing state between Electron's main process and renderer process(es) can be cumbersome, often involving manual setup of IPC channels and boilerplate code. electron-pronto-interconnect simplifies this by providing:

  • Easy State Exposure: Expose variables from your main process with a simple function call.
  • React Hooks: Consume and update these variables in your React components using familiar hook patterns (useState-like).
  • Two-way Synchronization: Changes in the renderer are reflected in the main process, and changes in the main process are broadcast to all listening renderers.
  • Promise-based RPC: Securely expose main process functions and call them from the renderer with a clean async/await pattern.
  • Type Safety: Written in TypeScript to provide strong typing for your synchronized state.
  • Minimal Boilerplate: Focus on your application logic, not on IPC plumbing.

Features

  • Expose variables from the main process to multiple renderer windows.
  • Update main process variables from renderer components.
  • Programmatically update variables from the main process and notify all renderers.
  • Expose main process functions to be called from the renderer, returning a Promise with the result.
  • Customizable setter logic in the main process when a variable is updated.
  • useIPC and useIPCState React hooks for seamless state integration in renderer processes.
  • errand helper function for clean, async/await based function calls.
  • Optional verbose logging on a per-state or per-function basis for easier debugging.
  • Secure communication using Electron's contextBridge via a dedicated preload script.

Prerequisites

This package is designed for use with Electron applications that utilize React. It requires:

  • Electron: Version 24.0.0 or higher.
  • React: Version 17.0.0 or higher.

Installation

npm install electron-pronto-interconnect

How it Works

This library provides two primary communication patterns:

  1. State Synchronization: You expose a variable from your main process. The renderer uses the useIPCState hook, which communicates with the main process to get the initial state and listen for updates. When you update the state using the hook's setter, the new value is sent to the main process.
  2. Remote Procedure Call (RPC): You register a function in your main process. The renderer can then errand this function. This sends an invoke request to the main process and returns a Promise, allowing for a clean async/await workflow to get results back.

Both patterns rely on a Preload Script to securely expose the necessary IPC communication functions (phaseSyncAPI) to the renderer process using Electron's contextBridge.

  1. Main Process: You expose a variable from your Electron main process. This sets up IPC listeners for getting and setting this variable. You provide a setter function that dictates how the variable is updated in the main process's scope.
  2. Preload Script: The package provides a preload script that securely exposes IPC communication functions (phaseSyncAPI) to the renderer process using Electron's contextBridge.
  3. Renderer Process: In your React components, you use the useIPC or useIPCState hook. These hooks communicate with the main process via the exposed phaseSyncAPI to get the initial state and listen for updates. When you update the state using the hook's setter, the new value is sent to the main process.

Usage

Here's how to set up and use electron-pronto-interconnect:

1. Main Process Setup

In your Electron main process file (e.g., main.ts or main.js):

// main.ts
import { app, BrowserWindow } from 'electron';
import { expose, updateAndNotify } from 'electron-pronto-interconnect/manager';
// If you only need 'expose', you can also use the default export:
// import { expose } from 'electron-pronto-interconnect';
import path from 'path';

// Example: Managing a global theme state
let currentTheme: 'light' | 'dark' = 'light';

// This function is called when the 'theme' variable is updated from any renderer
// or via updateAndNotify.
const mainProcessThemeSetter = (newTheme: 'light' | 'dark') => {
    console.log(`[Main Process] Theme changed from ${currentTheme} to ${newTheme}`);
    currentTheme = newTheme;
    // Add any other logic, like saving to a file or updating other main process state.
};

// --- RPC (Function Call) Example ---
const openFile = async () => {
    const { canceled, filePaths } = await dialog.showOpenDialog({});
    if (canceled || filePaths.length === 0) {
        return null; // Return null if the user cancels
    }
    return filePaths[0];
};

function createWindow() {
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            // Crucial: Path to the preload script provided by this package
            preload: path.join(__dirname, 'node_modules/electron-pronto-interconnect/dist/preload.js'),
            contextIsolation: true, // Recommended for security
            nodeIntegration: false,  // Recommended for security
        }
    });

    // Expose the 'theme' variable.
    // 'currentTheme' is its initial value.
    // 'mainProcessThemeSetter' is the callback to update it in the main process scope.
    expose<'light' | 'dark'>('theme', currentTheme, mainProcessThemeSetter);

    // Register the 'open-file' function so the renderer can call it.
    // This one will be silent by default.
    register('open-file', openFile);
    
    mainWindow.loadFile('index.html'); // Your renderer's HTML file

    // Example: Programmatically update the theme from the main process after 5 seconds
    setTimeout(() => {
        if (currentTheme !== 'dark') {
            console.log('[Main Process] Programmatically updating theme to dark.');
            updateAndNotify<'light' | 'dark'>('theme', 'dark');
        }
    }, 5000);
}

app.whenReady().then(() => {
    createWindow();

    app.on('activate', function () {
        if (BrowserWindow.getAllWindows().length === 0) createWindow();
    });
});

app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') app.quit();
});

Important Note for Preload Script: Ensure the preload path in webPreferences correctly points to node_modules/electron-pronto-interconnect/dist/preload.js. You might need to adjust this path based on your project structure or if you're using a bundler that copies assets.

2. Preload Script Setup (User-Managed)

electron-pronto-interconnect allows you to integrate its IPC bridge into your own custom Electron preload script. This gives you the flexibility to expose other APIs to your renderer process as needed.

Steps:

  1. Import phaseSyncAPI into Your Preload Script: In your preload script, import the phaseSyncAPI object from electron-pronto-interconnect:

    // In your project's preload.ts (e.g., src/preload.ts)
    import { contextBridge } from 'electron';
    import { phaseSyncAPI, type PhaseSyncAPI } from 'electron-pronto-interconnect/preload'; // Import from our package
    
    // You can define other APIs you want to expose here
    const myCustomAPI = {
        getAppVersion: () => '1.2.3',
        doSomethingElse: (data: any) => console.log('Custom API called with:', data),
    };
    
    // Expose the phaseSyncAPI for electron-pronto-interconnect
    contextBridge.exposeInMainWorld('phaseSync', phaseSyncAPI);
    
    // Expose your other custom APIs
    contextBridge.exposeInMainWorld('myCustomAPI', myCustomAPI);
    
    console.log('Preload script executed, phaseSync and myCustomAPI exposed.');

    (Ensure PhaseSyncAPI type is also exported from your package's preload.ts if users want to type their window.phaseSync object accurately, or they can define it themselves based on the phaseSyncAPI structure.)

  2. Configure Your BrowserWindow: In your Electron main process file, ensure the webPreferences.preload option points to your compiled preload script.

    // In your project's main.ts
    import { app, BrowserWindow } from 'electron';
    import path from 'path';
    
    function createWindow() {
        const mainWindow = new BrowserWindow({
            width: 800,
            height: 600,
            webPreferences: {
                // Point to YOUR OWN compiled preload script
                preload: path.join(__dirname, 'preload.js'), // Adjust path as needed
                contextIsolation: true,  // Crucial for contextBridge security
                nodeIntegration: false, // Recommended for renderer security
            }
        });
        // ...
        mainWindow.loadFile('index.html');
    }
    
    app.whenReady().then(createWindow);
    // ...

    Make sure that path.join(__dirname, 'preload.js') correctly resolves to the output location of your compiled preload script (e.g., if your preload.ts is in src/, and your outDir in tsconfig.json is dist/, then the path might be path.join(__dirname, 'dist/preload.js') relative to your compiled main process file).

Why this approach?

  • Flexibility: You control what's exposed to your renderer and under what names.
  • Centralization: All your contextBridge exposures are in one place (your preload script).
  • Security: You maintain control over the security boundary between your main process and renderer.

Remember to have contextIsolation: true in your webPreferences, as contextBridge relies on it.

3. Renderer Process (React Component)

In your React components, use the useIPC or useIPCState hooks:

// src/components/ThemeSwitcher.tsx
import React from 'react';
import { useIPCState } from 'electron-pronto-interconnect/renderer';

// Define the type for your shared state for clarity
type Theme = 'light' | 'dark';

function ThemeSwitcher(): JSX.Element {
    // 'theme' is the variableName you used in `expose` in the main process.
    // 'light' is the initial default value for the React state.
    // The hook will fetch the actual value from the main process.
    const [currentTheme, setCurrentTheme] = useIPCState<Theme>('theme', 'light');

    const toggleTheme = () => {
        const newTheme = currentTheme === 'light' ? 'dark' : 'light';
        setCurrentTheme(newTheme); // This will update the main process via IPC
    };

    return (
        <div>
            <p>Current application theme: <strong>{currentTheme}</strong></p>
            <button onClick={toggleTheme}>
                Switch to {currentTheme === 'light' ? 'Dark' : 'Light'} Mode
            </button>
        </div>
    );
}

export default ThemeSwitcher;

And ensure your renderer declares the global phaseSync type (optional, but good for TypeScript):

// src/renderer.d.ts or a global type definition file

// Import the PhaseSync interface from the package's preload types
import type { PhaseSync } from 'electron-pronto-interconnect/preload';

declare global {
    interface Window {
        phaseSync: PhaseSync;
    }
}

4. Using RPC (Remote Procedure Call)

In your renderer, you can call functions registered in the main process using the errand function:

// src/components/FileManager.tsx
import React, { useState } from 'react';
import { errand } from 'electron-pronto-interconnect/renderer';

function FileManager(): JSX.Element {
    const [selectedFile, setSelectedFile] = useState<string | null>(null);
    const [error, setError] = useState<string | null>(null);

    const handleOpenFile = async () => {
        try {
            setError(null);
            // Call the main process function and wait for the result
            const filePath = await errand<string | null>('open-file');
            
            if (filePath) {
                setSelectedFile(filePath);
            } else {
                // User cancelled the dialog
                setSelectedFile('User cancelled.');
            }
        } catch (err) {
            // This will catch errors if the function fails in the main process
            console.error('Failed to open file dialog:', err);
            setError('An error occurred while opening the file.');
        }
    };

    return (
        <div>
            <button onClick={handleOpenFile}>Open a File</button>
            {selectedFile && <p>Selected: <strong>{selectedFile}</strong></p>}
            {error && <p style={{ color: 'red' }}>{error}</p>}
        </div>
    );
}

export default FileManager;

API Reference

Main Process (electron-pronto-interconnect/manager)

  • expose<T>(variableName: string, initialValue: T, mainProcessSetter: (value: T) => void, options?: { verbose?: boolean }): void

    Exposes a variable for synchronization.

    • variableName: Unique string identifier for the state.
    • initialValue: The starting value of the variable.
    • mainProcessSetter: A callback function that's executed in the main process when the variable's value is changed from a renderer or by updateAndNotify. This function is responsible for actually updating the variable in the main process's scope.
    • options?: Optional. If verbose is true, logs GET/SET events for this variable to the console. Defaults to false.
  • updateAndNotify<T>(variableName: string, newValue: T): void

    Allows the main process to programmatically update an exposed variable and notify all listening renderers. Logging for this action is controlled by the verbose option set during the initial expose.

  • register(functionName: string, handler: (...args: any[]) => any, options?: { verbose?: boolean }): void

    Registers a main process function so it can be called from a renderer.

    • functionName: Unique string identifier for the function.
    • handler: The async or sync function to execute. Its return value will be sent back to the renderer.
    • options?: Optional. If verbose is true, logs when this function is called. Defaults to false.

Preload (electron-pronto-interconnect/preload)

  • Exposes window.phaseSync to the renderer, with the following methods:
    • invoke<T>(channel: string, ...args: any[]): Promise<T>
    • send(channel: string, ...args: any[]): void
    • on<T = any>(channel: string, listener: (value: T) => void): () => void (returns a cleanup function to remove the listener)

Renderer (electron-pronto-interconnect/renderer)

  • useIPC<T>(variableName: string, initialClientValue?: T): [T | undefined, (newValue: T) => void]

    A React hook to synchronize with an Electron main process variable. Returns undefined until the value is fetched from the main process, or if initialClientValue is not provided. The setter function updates the state optimistically in the renderer and sends the new value to the main process.

  • useIPCState<T>(variableName: string, defaultAndInitialValue: T): [T, (newValue: T) => void]

    A stricter version of useIPC. Requires a defaultAndInitialValue which is used as the initial state and as a fallback if the main process value is undefined. Guarantees the returned state is always of type T.

  • errand<T>(functionName: string, ...args: unknown[]): Promise<T>

    Calls a function registered in the main process.

    • functionName: The name of the function to call.
    • ...args: Any arguments to pass to the main process function.
    • Returns: A Promise that resolves with the value returned by the handler in the main process, or rejects if an error occurs.

License

This project is licensed under the Apache-2.0 License - see the LICENSE file for details.

Author

Muhammad Nabeel Adzan

Contributing

Contributions are welcome! Please feel free to submit a pull request or open an issue. (Consider adding more details here if you have specific contribution guidelines).


Keywords: electron, react, ipc, state management, react hooks, typescript, rpc, remote procedure call, main process, async