electron-router-dom

repository·main·Indexed 41 days ago

https://github.com/daltonmenezes/electron-router-dom

A specialized adapter for react-router-dom designed for Electron applications. It enables consistent routing across development and production environments, supports independent window routing, and allows the main process to send query strings to the renderer process. Version 2.0 supports React Router v6.4 Data APIs and provides a type-safe API with TypeScript support.

Tokens
9.9K
Snippets
32
Records
40
Agent score
45%

What's inside electron-router-dom

  1. Overview of electron-router-dom features

    main

    electron-router-dom is an adapter designed to facilitate the integration of react-router-dom with Electron. It solves common issues encountered when using standard web routing in Electron environments, particularly regarding development vs. production consistency and multi-window support.

    Key capabilities include:

    • Production and Development Readiness: Works seamlessly across both environments.
    • Window Routing Support: Enables each Electron window to maintain its own independent routing state.
    • Query String Support: Allows the main process to send query strings to the renderer process.
    • Type-safe API: Designed for a high-quality developer experience with TypeScript support.
  2. Understand the structure of the `settings` object

    main

    The settings object is returned by the createElectronRouter function. It contains the configuration used to initialize the router. You can use this object to reuse settings across different parts of your application or to derive TypeScript types using typeof settings.

    // Example of deriving a type from the settings object
    type RouterSettings = typeof settings;
  3. Migrate from Electron Router DOM v1 to v2

    main

    Version 2.0 introduces breaking changes due to a complete rewrite aimed at supporting React Router v6.4 Data APIs and improving type safety. Key changes include:

    • API Simplification: createFileRoute and createURLRoute have been removed.
    • Route Registration: Use the registerRoute method provided by the object returned from createElectronRouter instead of manual URL/file loading logic.
    • Component Imports: The Route component is no longer exported from electron-router-dom. You must now import it from react-router-dom.
    • Typing: ids and query keys are now typed for better intellisense and safety.
    • Query Strings: Improved support for passing information between the main and renderer processes via query strings.

    For a detailed step-by-step process, refer to the Migration Guide.

  4. Enable HMR using Electron Vite

    main

    To enable Hot Module Replacement (HMR) when using Electron Vite, you must configure createElectronRouter with specific types and then register your routes in the main process using the registerRoute function. This allows the renderer process to reload automatically during development.

    1. Initialize the router: In a shared library file (e.g., src/lib/electron-router-dom.ts), call createElectronRouter and define the ids in the types configuration that correspond to your window IDs.
    2. Register the route: In your main process entry point (e.g., src/main/index.ts), call registerRoute for your main window. You must provide the id matching your configuration, the browserWindow instance, the devServerUrl (typically from process.env['ELECTRON_RENDERER_URL']), and the path to your htmlFile.
    // 1. Shared configuration (src/lib/electron-router-dom.ts)
    import { createElectronRouter } from 'electron-router-dom'
    
    export const { Router, registerRoute, settings } = createElectronRouter({
      types: {
        ids: ['main'],
      },
    })
    
    // 2. Main process registration (src/main/index.ts)
    import { registerRoute } from 'lib/electron-router-dom'
    
    async function createMainWindow() {
      const mainWindow = new BrowserWindow({ /* ... */ })
    
      registerRoute({
        id: 'main',
        browserWindow: mainWindow,
        devServerUrl: process.env['ELECTRON_RENDERER_URL'],
        htmlFile: path.join(__dirname, '../renderer/index.html'),
      })
    }
  5. Type URLSearchParams globally for query keys

    main

    To enable TypeScript intellisense for URLSearchParams methods (like .get()) based on the queryKeys you defined in createElectronRouter, you can augment the global URLSearchParams interface. This works for both the global object and the useSearchParams hook from react-router-dom.

    Use the settings object returned by createElectronRouter to derive the types.

    import { createElectronRouter, type Query } from 'electron-router-dom'
    
    export const { Router, registerRoute, settings } = createElectronRouter({
      port: 4927,
    
      types: {
        ids: ['main'],
        queryKeys: ['name', 'version'],
      },
    })
    
    declare global {
      interface URLSearchParams {
        get<T extends typeof settings>(name: Query.Keys<T>): Query.Return
      }
    }
  6. Register a window route in the Main Process

    main

    In your main process (e.g., src/main/index.ts), use the registerRoute method to associate an Electron BrowserWindow with a specific route ID.

    Important: Electron Router DOM manages loadURL and loadFile internally. Do not call these methods manually on the BrowserWindow to avoid conflicts.

    import { registerRoute } from '../lib/electron-router-dom'
    import path from 'path'
    
    // Inside your window creation function:
    registerRoute({
      id: 'main',
      browserWindow: window,
      htmlFile: path.join(__dirname, '../renderer/index.html'),
    })
  7. Type all URLSearchParams methods globally

    main

    For full type safety across all URLSearchParams operations, you can augment the entire interface. This ensures that set, append, delete, has, forEach, getAll, keys, and entries are all aware of your specific queryKeys via the settings object.

    import { createElectronRouter, type Query } from 'electron-router-dom'
    
    export const { Router, registerRoute, settings } = createElectronRouter({
      port: 4927,
    
      types: {
        ids: ['main'],
        queryKeys: ['name', 'version'],
      },
    })
    
    declare global {
      type Types = typeof settings
    
      interface URLSearchParams {
        get<T extends Types>(name: Query.Keys<T>): Query.Return
        set<T extends Types>(name: Query.Keys<T>, value: string): void
        append<T extends Types>(name: Query.Keys<T>, value: string): void
        delete<T extends Types>(name: Query.Keys<T>, value?: string): void
        has<T extends Types>(name: Query.Keys<T>, value?: string): boolean
        forEach<T extends Types>(
          callbackfn: (
            value: string,
            key: Query.Keys<T>,
            parent: URLSearchParams
          ) => void
        ): void
        getAll<T extends Types>(name: Query.Keys<T>): string[]
        keys<T extends Types>(): IterableIterator<Query.Keys<T>>
        entries<T extends Types>(): IterableIterator<[Query.Keys<T>, string]>
      }
    }
  8. How to use the Context API with Electron Router DOM

    main

    When using the React Context API with Electron Router DOM, you must ensure that your Provider components are placed correctly within the routing hierarchy.

    The Constraint: You cannot use a Provider at the same level as a Route inside a <Routes> component. React Router DOM requires that all direct children of <Routes> must be a <Route> or a <React.Fragment>. If you attempt to place a Provider directly inside <Routes>, you will encounter the following error:

    🚫 Error: [AppProvider] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

    Correct Patterns: There are two primary ways to correctly implement a Provider:

    1. Inside the element prop: Wrap the component being rendered by the route within the Provider inside the element prop of a <Route>.
    2. Wrapping the Router: Wrap the entire <Router> component with your Provider so that the router and all its internal components have access to the context.
    // Pattern 1: Using the Provider in the element prop
    <Route
      path="/"
      element={
        <SomeProvider>
          <SomePage />
        </SomeProvider>
      }
    />
    
    // Pattern 2: Wrapping the Router with the Provider
    <SomeProvider>
      <Router
        main={
          <Route
            path="/"
            element={<SomePage />}
          />
        }
      />
    </SomeProvider>
  9. Configure application routes in the Renderer Process

    main

    In your renderer process (e.g., src/renderer/routes.tsx), use the Router component to define which react-router-dom routes belong to which window ID.

    The Router component accepts props named after the IDs you defined in createElectronRouter. Each prop receives a fragment of react-router-dom routes.

    import { Router } from '../lib/electron-router-dom'
    import { Route } from 'react-router-dom'
    
    import { MainScreen, AboutScreen, SearchScreen } from './screens'
    
    export function AppRoutes() {
      return (
        <Router
          main={
            <>
              <Route path="/" element={<MainScreen />} />
              <Route path="/search" element={<SearchScreen />} />
            </>
          }
          about={<Route path="/" element={<AboutScreen />} />}
        />
      )
    }
  10. Type a window factory using registerRoute types

    main

    If you use a factory function to create Electron windows, you can ensure the id and query parameters are correctly typed by extracting the type from the registerRoute function returned by createElectronRouter.

    1. Extract the Route type using Parameters<typeof registerRoute>[0].
    2. Use Route['id'] and Route['query'] in your factory's props interface.
    import { registerRoute } from './lib/electron-router-dom'
    
    type Route = Parameters<typeof registerRoute>[0]
    
    interface WindowProps extends Electron.BrowserWindowConstructorOptions {
      id: Route['id']
      query?: Route['query']
    }
    
    export function createWindow({ id, query, ...options }: WindowProps) {
      const window = new BrowserWindow(options)
    
      registerRoute({
        id,
        query,
        browserWindow: window,
        htmlFile: path.join(__dirname, '../renderer/index.html'),
      })
    
      return window
    }