0.1.2 • Published 1 year ago

electron-shared-state-react v0.1.2

Weekly downloads
-
License
MIT
Repository
-
Last release
1 year ago

Electron Shared State with React

Share state between Electron renderers and main processes

Sharing state between multiple renderers shouldn't be hard. So we fixed it by adding simple React hooks similar to useState.

Example:

export function Settings() {
  const [settings, setSettings] = useSettings('test', [])
  const add = () => setSettings([...settings, 'entry'])

  return (
    <div>
      <button onClick={add}>Add</button>
      {settings.map((s, i) => (
        <div key={`entry-${i}`}>{s}</div>
      ))}
    </div>
  )
}

Getting Started

First, install via yarn or npm npm install electron-shared-state-react to your electron application.

1. Initialize the manager in your Electron main process

There are two sets of managers:

  1. SettingsManager

    With this manager you can persist to disk a set of settings. This is great to store application state.

  2. GlobalStateManager

    With this manager you can syncronize runtime state between the renderer and main processes, but this data will not be persisted.

Ensure that you also dispose these properly at the end of your application lifecycle in your electron main process.

import {
  GlobalSharedStateManager,
  SettingsManager,
} from 'electron-shared-state-react/dist/main'

app.on('ready', async () => {
  ...
  await SettingsManager.ready()
  GlobalSharedStateManager.ready()
  ...
}

app.on('before-quit', async () => {
  ...
  SettingsManager.quit()
  GlobalSharedStateManager.quit()
  ...
}

2. Expose the ipc bridge in your preload.ts for all renderers

We need to expose three methods via Electron's content bridge, so we can communicate with our main process.

preload.ts Example

import { contextBridge, ipcRenderer } from 'electron'
import {
  Platform,
  StateChannel,
} from 'electron-shared-state-react/dist/renderer/platform'

function getStateForChannel<T>(
  channel: StateChannel,
  key: string,
): Promise<{ value: T; hasKey: boolean }> {
  return ipcRenderer.invoke(`${channel}:getState`, key)
}

function setStateForChannel<T>(
  channel: StateChannel,
  key: string,
  value: T | undefined,
  source: string | undefined,
): Promise<void> {
  return ipcRenderer.invoke(`${channel}:setState`, key, value, source)
}

function subscribeToKey<T>(
  channel: StateChannel,
  key: string,
  updatedValue: (\_: any, \_value: T) => void,
): () => void {
  ipcRenderer.addListener(
    `${channel}:updatedValue:${key}`,
    updatedValue
  )
  ipcRenderer.send(`${channel}:watchKey`, key)

  getStateForChannel(channel, key).then((result) => {
    updatedValue(key, result.value as T)
  })

  return () => {
    ipcRenderer.removeListener(
      `${channel}:updatedValue:${key}`,
      updatedValue
    )
    ipcRenderer.send(`${channel}:unwatchKey`, key)
  }
}

const rendererPlatform: Platform = {
  setStateForChannel,
  subscribeToKey,
  getStateForChannel,
}

declare global {
  interface Window {
    // eslint-disable-next-line @typescript-eslint/ban-types
    electronAPI: {
      rendererPlatform: Platform
    }
  }
}

contextBridge.exposeInMainWorld('electronAPI', {
  rendererPlatform,
})

3. Initialize the platform now on all renderers

In order for our Electron renderer hooks and methods to work we need to first initialize the platform we exposed in the previous step.

Put this code anywhere in your renderer entry point (e.g. renderer.ts):

import { initializePlatform } from 'electron-shared-state-react/dist/renderer'
initializePlatform(window.electronAPI.rendererPlatform)

Use it anywhere in your React hooks

You can now use the settings or global state similar to a useState hook in your application:

import { useGlobalState } from 'electron-shared-state-react/dist/renderer/useGlobalState'

export function NumberIncrementer() {
  const [numberOfTasks, setNumberOfTasks] = useGlobalState('numberOfTasks', 0)
  return (
    <div>
      <button onClick={() => setNumberOfTasks(numberOfTasks + 1)}>
        Add Task
      </button>

      <div>{numberOfTasks} Tasks</div>
    </div>
  )
}

Use it outside of React

For settings use:

import {
  fetchSettings,
  setSetting,
} from 'electron-shared-state-react/dist/renderer/useSettings'

// fetch a setting
fetchSettings<string[]>('test').then((settings) => {
  console.log(settings)
})

// set a setting
await setSetting('test', ['test'])

For global state use:

import {
  fetchGlobalState,
  setGlobalState,
} from 'electron-shared-state-react/dist/renderer/useGlobalState'

// fetching a global state
fetchGlobalState<string[]>('test').then((test) => {
  console.log(test)
})

// setting a global state
await setGlobalState('test', ['test'])
0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago