Giter Club home page Giter Club logo

reduxtron's Introduction

reduxtron hero image

end-to-end electron state management

features

  1. frontend framework agnostic, use whatever you like, even vanilla js.
  2. global state, single-source of truth for all state you want to share.
  3. making the app state as predictable as any redux implementation (actions, devtools, etc)
  4. frontend, tray, main node process dispatch type-defined actions
  5. all above mentioned pieces receive the new state back thru redux subscriptions
  6. single place to write redux middleware with full node.js access (file-system, fetch, db, etc)
  7. easy way to persist and retrieve state (reading/writing to a json file, saving to a local db, fetching/posting to external api, etc)
  8. ipc performance, no api layer, without any manual ipc messaging/handling to write
  9. follows latest electron safety recommendations (sandbox: true + nodeIntegration: false + contextIsolation: true)

why reduxtron

the average redux setup on web applications (and plenty of tutorials for redux on electron) follows the simpler rule: keeping redux constrained to frontend.

diagram containing a typical react + redux setup

this ends up being pretty limiting because once you want to go past anything broader than a single window/tab of a default web application. there’s no clear or definitive way to share state or communicate between electron layers.

diagram containing a typical react + redux setup on the left, several electron and node.js specific api on the right, and a vertical line on the middle written 'inter-process'

that’s why reduxtron exist, it moves your state "one level up" on the three, to outside your frontend boundary into the broader electron main process.

reduxtron setup: with redux, business logic and node/electron api running on the main process and the web frontend on the renderer. all pieces connect to the redux piece using actions and subscriptions

with this setup you can both have a single state across all your electron app, without relying on a single browser view and also leverage the full potential of the electron and node APIs, without explicitly writing a single inter-process communication message.

the premise is simple: every piece of your app can communicate using the same redux™ way (using actions, subscriptions, and getState calls) to a single store.

repo organization

this is a monorepo containing the code for:

  1. the reduxtron library
  2. the reduxtorn demo app
  3. the reduxtron boilerplates:

the reduxtron library

set of utilities available on npm to plug into existing electron projects

on your terminal

# install as a regular dependency
npm i reduxtron

create your redux reducers somewhere where both main and renderer processes can import (for example purposes we’ll be considering a shared/reducers file). remember to export your State and Action types

initialize your redux store on the main process (we’ll be considering a main/store for this)

add the following lines onto your main process entry file:

import { app, ipcMain } from "electron";
import { mainReduxBridge } from "reduxtron/main";
import { store } from "shared/store";

const { unsubscribe } = mainReduxBridge(ipcMain, store);

app.on("quit", unsubscribe);

and this onto your preload entry file:

import { contextBridge, ipcRenderer } from "electron";
import { preloadReduxBridge } from "reduxtron/preload";
import type { State, Action } from "shared/reducers";

const { handlers } = preloadReduxBridge<State, Action>(ipcRenderer);

contextBridge.exposeInMainWorld("reduxtron", handlers);

this will populate a reduxtron object on your frontend runtime containing the 3 main redux store functions (inside the global/window/globalThis object):

// typical redux getState function, have the State return type defined as return
global.reduxtron.getState(): State

// typical redux dispatch function, have the Action type defined as parameter
global.reduxtron.dispatch(action: Action): void

// receives a callback that get’s called on each store update
// returns a `unsubscribe` function, you can optionally call it when closing window or when you don’t want to listen for changes anymore.
global.reduxtron.subscribe(callback: ((newState: State) => void) => () => void)

ps: the reduxtron key here is just an example, you can use any object key you prefer

demo app

a ever wip demo app to show off some of the features/patterns this approach enables

demo app screenshot

git clone [email protected]:vitordino/reduxtron.git # clone this repo
cd reduxtron # change directory to inside the repo
npm i # install dependencies
turbo demo # start demo app on development mode

the demo contains some nice (wip) features:

  1. naïve persistance (writing to a json file on every state change + reading it on initialization)

  2. zustand-based store and selectors (to prevent unnecessary rerenders)

  3. swr-like reducer to store data from different sources (currently http + file-system)

  4. micro-apps inside the demo:

    • a simple to do list with small additions (eg.: external windows to add items backed by different frontend frameworks)
    • a dog breed picker (to show off integration with http APIs)
    • a finder-like file explorer
  5. all the above micro-apps also have a native tray interface, always up-to-date, reads from the same state and dispatches the same actions

boilerplates

as aforementioned, this repo contains some (non-exhaustive, really simple) starters.

currently they are all based on electron-vite, only implements a counter, with a single renderer window and tray to interact with.

why redux?

spoiler: i’m not a die hard fan of redux nowadays

redux definitely helped a bunch of the early-mid 2010’s web applications. back then, we didn’t had that much nicer APIs to handle a bunch of state for us.

we now have way more tooling for the most common (and maybe worse) use-cases for redux:


so why redux was chosen?

  1. framework agnostic, it’s just javascript™ (so it can run on node, browser, or any other js runtime needed) — compared to (recoil, pinia)
  2. single store (compared to mobx, xstate and others)
  3. single "update" function, with a single signature (so it’s trivial to register on the preload and have end-to-end type-safety)
  4. single "subscribe" function to all the state — same as above reasons
  5. can use POJOs as data primitive (easy to serialize/deserialize on inter-process communication)

related projects and inspiration

while developing this repo, i also searched for what was out there™ in this regard, and was happy to see i wasn’t the only thinking on these crazy thoughts.

  • klarna/electron-redux

    • belongs to a major company, high visibility
    • started around 2016, but stopped being maintained around mid-2020
    • had another redux store on the frontend, and sync between them: a bit more complex than i’d like.
    • incompatible with electron versions >= 14
  • zoubingwu/electron-shared-state

    • individual-led, still relatively maintained
    • no redux, single function export
    • doesn’t respect electron safety recommendations (needs nodeIntegration: true + contextIsolation: false)

reduxtron's People

Contributors

vitordino avatar

Stargazers

Tạ Văn Trường avatar Alex Naibuu avatar  avatar Zation avatar  avatar  avatar  avatar João Marins avatar Sibelius Seraphini avatar nicolas lopes avatar

Watchers

 avatar  avatar

reduxtron's Issues

Thunk support?

This is a cool project, I'm suprised there's not more interest in it. I have an old app which uses a custom version of electron-redux, I'm looking to see if I can replace the state management with reduxtron. My app uses RTK heavily and thunks in particular don't seem to be supported currently -- dispatch in the boilerplate and demo packages only accepts an action.

Is this a planned feature, or is there another way to use thunks with reduxtron?

`useStore` doesn't work correctly.

Hi @vitordino

Thank you for your efforts and I love this package. I tried most of packages for redux in electron, but this is the best I think.

But when I am trying to use useStore I found that it was not updated even I dispatched it.

import { createUseStore } from 'reduxtron/zustand-store';

export const useStore: UseBoundStore<StoreApi<Partial<IAppState>>> =
  createUseStore<IAppState, Action>(window.reduxtron);

And I used useStore like below.

  const isDemo = useStore((state) => state.user?.settings.isDemo);
  const appDispatch = useAppDispatch();

  const handleSubmit = useCallback(async () => {
    appDispatch(toggleDemo(false));

I checked the dispatch was successfully applied to the store, but isDemo was not updated :(

Cant import { mainReduxBridge }

Greetings I cant import main mainReduxBridge in my project . ( also cant import preloadReduxBridge )

im using electron-vite-builder and the tsconfig there seems to cause the issue

I dont want to mess with their config ... so im not sure what to do :)

Cannot find module 'reduxtron/main' or its corresponding type declarations.
  There are types at '..../node_modules/reduxtron/dist/main.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'.ts(2307)

im currently debating if I build my own solution for handling state across multiple windows ...

already fiddled around abit . handling the state in the main process seemed like a good solution for me ... also I would need to persist the data and be able to debug / connect to the redux store ...

Previously I expermented with having a renderer store only .. but it got abit messy sharing the the state .. as there is no single source of truth .. still found a solution to that .. but wasnt happy ... so i ended up looking into other solutions ... electron-redux didnt seem to work with preload script / context isolation .. and reduxtron gave me trouble with import ... yeah :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.