### Install Zutron and Zustand Source: https://context7.com/goosewobbler/zutron/llms.txt Install Zutron and its peer dependency Zustand using npm, yarn, or pnpm. ```bash npm install zutron zustand # or pnpm add zutron zustand # or yarn add zutron zustand ``` -------------------------------- ### Install Zutron and Dependencies Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md Install Zutron and its peer dependency Zustand using npm. Other package managers like pnpm or yarn can also be used. ```bash npm i zutron zustand ``` -------------------------------- ### Uninstall Zutron and Install @zubridge/electron Source: https://github.com/goosewobbler/zutron/blob/main/README.md Use these commands to migrate from Zutron to its successor, @zubridge/electron. Update your imports accordingly. ```bash pnpm uninstall zutron pnpm install @zubridge/electron ``` -------------------------------- ### Instantiate Bridge in Main Process (Multi-Window) Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md Instantiate the Zutron bridge for a multi-window Electron application. This setup supports multiple windows and views, and provides a subscribe function to add new windows or views after initialization. ```typescript // `src/main/index.ts` import { app, BrowserWindow, WebContentsView } from 'electron'; import { mainZustandBridge } from 'zutron/main'; // create main window const mainWindow = new BrowserWindow({ ... }); // create secondary window const secondaryWindow = new BrowserWindow({ ... }); // instantiate bridge const { unsubscribe, subscribe } = mainZustandBridge(store, [mainWindow, secondaryWindow]); // unsubscribe on quit app.on('quit', unsubscribe); // create a view some time after the bridge has been instantiated const runtimeView = new WebContentsView({ ... }); // subscribe the view to store updates subscribe([runtimeView]); ``` -------------------------------- ### Increment Counter in Main Process Source: https://github.com/goosewobbler/zutron/blob/main/docs/usage-main-process.md Access the store directly in the main process to get the current state and update it. Updates are automatically propagated to subscribed renderer processes. ```typescript // `src/main/counter/index.ts` import { store } from '../store.js'; // increment counter const { counter } = store.getState(); store.setState({ counter: counter + 1 }); ``` -------------------------------- ### Create Preload Script Handlers Source: https://context7.com/goosewobbler/zutron/llms.txt The `preloadZustandBridge` function creates the preload script handlers that expose state management capabilities to the renderer process through Electron's `contextBridge`. It returns handlers for dispatching actions, getting state, and subscribing to state changes. ```typescript // src/preload/index.ts import { contextBridge } from 'electron'; import { preloadZustandBridge } from 'zutron/preload'; import type { Handlers } from 'zutron'; // Define your app state type (must match main process) type AppState = { counter: number; user: { name: string } | null; }; // Create the preload bridge const { handlers } = preloadZustandBridge(); // Expose handlers to renderer via contextBridge contextBridge.exposeInMainWorld('zutron', handlers); // TypeScript: Declare the window interface for type safety declare global { interface Window { zutron: Handlers; } } ``` -------------------------------- ### Define Root Reducer for Zutron Source: https://context7.com/goosewobbler/zutron/llms.txt Compose feature reducers into a root reducer for managing the application's overall state. This example combines the counter reducer. ```typescript // src/features/index.ts import type { Reducer } from 'zutron'; import { counterReducer } from './counter/index.js'; export type AppState = { counter: number }; export const rootReducer: Reducer = (state, action) => ({ counter: counterReducer(state.counter, action), }); ``` -------------------------------- ### Create Zustand Store (TypeScript) Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md Create a Zustand store for your application state in the main process. Provide your application state type if using TypeScript. ```typescript // `src/main/store.ts` import { createStore } from 'zustand/vanilla'; import type { AppState } from '../features/index.js'; const initialState: AppState = { counter: 0, ui: { ... } }; // create app store export const store = createStore()(() => initialState); ``` -------------------------------- ### Initialize Main Process Bridge with Reducers Source: https://context7.com/goosewobbler/zutron/llms.txt Initialize the main process bridge with a root reducer. This allows Zutron to manage state updates through dispatched actions. ```typescript // src/main/index.ts import { createStore } from 'zustand/vanilla'; import { mainZustandBridge } from 'zutron/main'; import { rootReducer, type AppState } from '../features/index.js'; const store = createStore()(() => ({ counter: 0 })); const { unsubscribe } = mainZustandBridge(store, [mainWindow], { reducer: rootReducer, }); ``` -------------------------------- ### Instantiate Bridge in Main Process (Single Window) Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md Instantiate the Zutron bridge in the main process for a single-window Electron application. The bridge requires the store and an array of window objects. An unsubscribe function is returned to deactivate the bridge. ```typescript // `src/main/index.ts` import { app, BrowserWindow } from 'electron'; import { mainZustandBridge } from 'zutron/main'; // create main window const mainWindow = new BrowserWindow({ ... }); // instantiate bridge const { unsubscribe } = mainZustandBridge(store, [mainWindow]); // unsubscribe on quit app.on('quit', unsubscribe); ``` -------------------------------- ### Instantiate Bridge in Preload Script (TypeScript) Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md Instantiate the Zutron bridge in the preload script. Expose the bridge handlers to the renderer process using Electron's contextBridge. Provide the application state type if using TypeScript. ```typescript // `src/preload/index.ts` import { contextBridge } from 'electron'; import { preloadZustandBridge } from 'zutron/preload'; import type { Handlers } from 'zutron'; import type { AppState } from '../features/index.js'; // instantiate bridge export const { handlers } = preloadZustandBridge(); // expose handlers to renderer process contextBridge.exposeInMainWorld('zutron', handlers); // declare handlers type declare global { interface Window { zutron: Handlers; } } ``` -------------------------------- ### Create Dispatch Helper for Main Process Source: https://context7.com/goosewobbler/zutron/llms.txt Utilize createDispatch in the main process to mirror renderer dispatch functionality. This allows dispatching actions from various main process handlers like system trays or IPC events. The dispatch helper can be configured with reducers, handlers, or used directly with the store. ```typescript // src/main/dispatch.ts import { createDispatch } from 'zutron/main'; import { store } from './store.js'; import { rootReducer } from '../features/index.js'; // For reducer-based setup export const dispatch = createDispatch(store, { reducer: rootReducer }); // For separate handlers setup // export const dispatch = createDispatch(store, { handlers: actionHandlers(store, initialState) }); // For store-based handlers (default, no options needed) // export const dispatch = createDispatch(store); ``` ```typescript // src/main/tray/index.ts import { Menu, Tray } from 'electron'; import { dispatch } from '../dispatch.js'; export function createTray(iconPath: string) { const tray = new Tray(iconPath); const contextMenu = Menu.buildFromTemplate([ { label: 'Increment Counter', click: () => dispatch('COUNTER:INCREMENT'), }, { label: 'Reset', click: () => dispatch('STORE:RESET'), }, { label: 'Async Operation', click: () => { const thunk = (getState: Function, dispatch: Function) => { console.log('Current state:', getState()); dispatch('COUNTER:INCREMENT'); }; dispatch(thunk); }, }, ]); tray.setContextMenu(contextMenu); return tray; } ``` -------------------------------- ### Initialize Main Process Bridge with Zutron Source: https://context7.com/goosewobbler/zutron/llms.txt Initialize the IPC bridge in the main process using `mainZustandBridge`. This connects your Zustand store to renderer processes. Ensure to clean up the bridge on application quit. ```typescript // src/main/index.ts import path from 'node:path'; import { BrowserWindow, app } from 'electron'; import { createStore } from 'zustand/vanilla'; import { mainZustandBridge } from 'zutron/main'; // Define your application state type type AppState = { counter: number; user: { name: string } | null; }; // Create the store with initial state const initialState: AppState = { counter: 0, user: null }; const store = createStore()(() => initialState); // Create main window with security best practices const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { contextIsolation: true, sandbox: true, nodeIntegration: false, preload: path.join(__dirname, 'preload.js'), }, }); // Initialize the bridge with store and windows const { unsubscribe, subscribe } = mainZustandBridge(store, [mainWindow]); // Clean up on app quit app.on('quit', unsubscribe); // Add runtime windows to the bridge const secondaryWindow = new BrowserWindow({ /* ... */ }); subscribe([secondaryWindow]); ``` -------------------------------- ### Create Main Process Dispatch with Reducer Option Source: https://github.com/goosewobbler/zutron/blob/main/docs/usage-main-process.md If using Redux-style reducers, pass the root reducer to `createDispatch` using the `reducer` option. ```typescript // `src/main/dispatch.ts` import { createDispatch } from 'zutron/main'; import { store } from './store.js'; import { rootReducer } from '../features/index.js'; export const dispatch = createDispatch(store, { reducer: rootReducer }); ``` -------------------------------- ### Instantiate Bridge with Reducer Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md Instantiate the Zutron bridge in the main process, providing a root reducer for state management. This approach is suitable for applications using Redux-style reducers. ```typescript // `src/main/index.ts` import { app, BrowserWindow } from 'electron'; import { mainZustandBridge } from 'zutron/main'; import { rootReducer } from '../features/index.js' // create main window const mainWindow = new BrowserWindow({ ... }); // instantiate bridge const { unsubscribe } = mainZustandBridge(store, [mainWindow], { reducer: rootReducer }); // unsubscribe on quit app.on('quit', unsubscribe); ``` -------------------------------- ### Create useStore Hook in Renderer Process Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md This hook is used in the renderer process to access the Zutron store. It requires the `window.zutron` object, which is exposed by the preload bridge. ```typescript // `src/renderer/hooks/useStore.ts` import { createUseStore } from 'zutron'; import type { AppState } from '../../features/index.js'; export const useStore = createUseStore(window.zutron); ``` -------------------------------- ### Instantiate Bridge with Custom Handlers Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md Instantiate the Zutron bridge in the main process, passing custom action handlers if they are separate from the store object. This is useful for organizing store logic. ```typescript // `src/main/index.ts` import { mainZustandBridge } from 'zutron/main'; import { actionHandlers } from '../features/index.js'; // create handlers for store const handlers = actionHandlers(store, initialState); // instantiate bridge const { unsubscribe } = mainZustandBridge(store, [mainWindow], { handlers }); ``` -------------------------------- ### Dispatch Actions and Thunks in Main Process Source: https://github.com/goosewobbler/zutron/blob/main/docs/usage-main-process.md Use the main process dispatch helper to dispatch simple actions or thunk functions. Thunks receive `getState` and `dispatch` as arguments. ```typescript // `src/main/counter/index.ts` import { dispatch } from '../dispatch.js'; // dispatch action dispatch('COUNTER:INCREMENT'); const onIncrementThunk = (getState, dispatch) => { // do something based on the store ... // dispatch action dispatch('COUNTER:INCREMENT'); }; // dispatch thunk dispatch(onIncrementThunk); ``` -------------------------------- ### Create Main Process Dispatch with Handlers Option Source: https://github.com/goosewobbler/zutron/blob/main/docs/usage-main-process.md When store handler functions are separate from the store object, pass them using the `handlers` option to `createDispatch`. ```typescript // `src/main/dispatch.ts` import { createDispatch } from 'zutron/main'; import { store } from './store.js'; import { actionHandlers } from '../features/index.js'; export const dispatch = createDispatch(store, { handlers: actionHandlers(store, initialState) }); ``` -------------------------------- ### Create Main Process Dispatch Helper Source: https://github.com/goosewobbler/zutron/blob/main/docs/usage-main-process.md Create a dispatch helper function for the main process using `createDispatch`. This function mirrors the functionality of the renderer process's `useDispatch` hook. ```typescript // `src/main/dispatch.ts` import { createDispatch } from 'zutron/main'; import { store } from './store.js'; export const dispatch = createDispatch(store); ``` -------------------------------- ### Direct Store Access in Main Process Source: https://context7.com/goosewobbler/zutron/llms.txt Access the Zustand store directly in the main process using the vanilla API. State changes made here automatically synchronize with subscribed renderer processes. You can read state, update state directly using setState, and subscribe to state changes. ```typescript // src/main/store.ts import { createStore } from 'zustand/vanilla'; type AppState = { counter: number; lastUpdated: number }; export const store = createStore()(() => ({ counter: 0, lastUpdated: Date.now(), })); ``` ```typescript // src/main/background-tasks.ts import { store } from './store.js'; // Read current state const currentState = store.getState(); console.log('Counter value:', currentState.counter); // Update state directly - changes sync to renderer automatically store.setState({ counter: currentState.counter + 10, lastUpdated: Date.now(), }); // Subscribe to state changes const unsubscribe = store.subscribe((state, prevState) => { if (state.counter !== prevState.counter) { console.log('Counter changed from', prevState.counter, 'to', state.counter); } }); // Get initial state const initialState = store.getInitialState(); console.log('Initial counter was:', initialState.counter); // Cleanup subscription when done unsubscribe(); ``` -------------------------------- ### Access Store in Renderer with useStore Source: https://github.com/goosewobbler/zutron/blob/main/docs/usage-renderer-process.md Use the `useStore` hook to access the store's state in the renderer process. Ensure you import it from '../hooks/useStore.js'. ```typescript // `src/renderer/counter/index.ts` import { useStore } from '../hooks/useStore.js'; const counter = useStore((x) => x.counter); ``` -------------------------------- ### Dispatch Actions and Thunks in Renderer Source: https://github.com/goosewobbler/zutron/blob/main/docs/usage-renderer-process.md Utilize the `useDispatch` hook to dispatch actions and thunks. The `dispatch` function is exported from 'zutron' and requires the Zutron instance. ```typescript // `src/renderer/dispatch.ts` import { useDispatch } from 'zutron'; export const dispatch = useDispatch(window.zutron); ``` ```typescript // `src/renderer/counter/index.ts` import { dispatch } from '../dispatch.js'; // dispatch action dispatch('COUNTER:INCREMENT'); const onIncrementThunk = (getState, dispatch) => { // do something based on the store ... // dispatch action dispatch('COUNTER:INCREMENT'); }; // dispatch thunk dispatch(onIncrementThunk); ``` -------------------------------- ### Create React UseStore Hook Source: https://context7.com/goosewobbler/zutron/llms.txt The `createUseStore` function creates a React hook for accessing the synchronized store state in renderer processes. It accepts the handlers exposed by the preload script and returns a hook compatible with Zustand's selector pattern. ```typescript // src/renderer/hooks/useStore.ts import { createUseStore } from 'zutron'; type AppState = { counter: number; user: { name: string } | null; }; // Create the useStore hook with the exposed handlers export const useStore = createUseStore(window.zutron); // src/renderer/components/Counter.tsx import { useStore } from '../hooks/useStore.js'; export function Counter() { // Select specific state slices for optimized re-renders const counter = useStore((state) => state.counter); const user = useStore((state) => state.user); // Access full state when needed const fullState = useStore(); return (

Count: {counter}

User: {user?.name ?? 'Not logged in'}

); } ``` -------------------------------- ### Define Store-based Action Handlers Source: https://context7.com/goosewobbler/zutron/llms.txt Action handlers are defined directly on the store object. This is the default pattern and requires no additional configuration when calling `mainZustandBridge`. Handlers are attached to the store during creation and can be dispatched by name. ```typescript // src/features/counter/index.ts import type { Store } from '../main/store.js'; export const handlers = (setState: Store['setState']) => ({ 'COUNTER:INCREMENT': () => setState((state) => ({ counter: state.counter + 1 })), 'COUNTER:DECREMENT': () => setState((state) => ({ counter: state.counter - 1 })), }); // src/features/index.ts import { handlers as counterHandlers } from './counter/index.js'; import type { Store } from '../main/store.js'; export type AppState = { counter: number }; export const actionHandlers = (setState: Store['setState'], initialState: AppState) => ({ ...counterHandlers(setState), 'STORE:RESET': () => setState(() => initialState), }); // src/main/store.ts import { createStore } from 'zustand/vanilla'; import { actionHandlers, type AppState } from '../features/index.js'; const initialState: AppState = { counter: 0 }; // Handlers are attached directly to the store export const store = createStore()((setState) => ({ ...initialState, ...actionHandlers(setState, initialState), })); // src/main/index.ts - no handlers option needed const { unsubscribe } = mainZustandBridge(store, [mainWindow]); ``` -------------------------------- ### TypeScript Type Definitions for Zutron Source: https://context7.com/goosewobbler/zutron/llms.txt Import and utilize Zutron's core types for Redux-style reducers, actions, async thunks, and preload bridge handlers to ensure type safety in your Electron application. ```typescript import type { Action, Thunk, Reducer, Handlers, AnyState, Dispatch, } from 'zutron'; // Define your application state type AppState = { counter: number; todos: Array<{ id: string; text: string; done: boolean }>; }; // Type-safe action definition type AppAction = | Action<'COUNTER:INCREMENT'> | Action<'COUNTER:SET'> | Action<'TODO:ADD'> | Action<'TODO:TOGGLE'>; // Type-safe reducer const appReducer: Reducer = (state, action) => { switch (action.type) { case 'COUNTER:INCREMENT': return { ...state, counter: state.counter + 1 }; case 'TODO:ADD': const newTodo = { id: crypto.randomUUID(), text: action.payload as string, done: false }; return { ...state, todos: [...state.todos, newTodo] }; default: return state; } }; // Type-safe thunk const fetchAndUpdateCounter: Thunk = async (getState, dispatch) => { const response = await fetch('/api/counter'); const { value } = await response.json(); dispatch('COUNTER:SET', value); }; // Type-safe handlers in preload declare global { interface Window { zutron: Handlers; } } ``` -------------------------------- ### Dispatch Actions with useDispatch Hook Source: https://context7.com/goosewobbler/zutron/llms.txt Use the useDispatch hook in renderer processes to dispatch string actions, action objects, or thunk functions. Ensure the AppState type is correctly defined. ```typescript // src/renderer/hooks/useDispatch.ts import { useDispatch } from 'zutron'; type AppState = { counter: number }; export const dispatch = useDispatch(window.zutron); ``` ```typescript // src/renderer/components/CounterControls.tsx import { dispatch } from '../hooks/useDispatch.js'; export function CounterControls() { // Dispatch string action const handleIncrement = () => dispatch('COUNTER:INCREMENT'); // Dispatch string action with payload const handleSetValue = (value: number) => dispatch('COUNTER:SET', value); // Dispatch action object const handleDecrement = () => dispatch({ type: 'COUNTER:DECREMENT', payload: null }); // Dispatch thunk for async operations const handleAsyncIncrement = () => { const thunk = (getState: () => AppState, dispatch: Function) => { const currentValue = getState().counter; console.log('Current counter:', currentValue); // Perform async operation setTimeout(() => { dispatch('COUNTER:INCREMENT'); }, 1000); }; dispatch(thunk); }; return (
); } ``` -------------------------------- ### Define Root Reducer for Store Source: https://github.com/goosewobbler/zutron/blob/main/docs/getting-started.md Define a root reducer for your application state, combining individual reducers for different parts of the state. This is used when the main process bridge needs to manage state updates via reducers. ```typescript // `src/features/index.ts` import type { Reducer } from 'zutron'; import { counterReducer } from '../features/counter/index.js'; import { uiReducer } from '../features/ui/index.js'; export type AppState = { counter: number ui: { ... } }; // create root reducer export const rootReducer: Reducer = (state, action) => ({ counter: counterReducer(state.counter, action), ui: uiReducer(state.ui, action) }); ``` -------------------------------- ### Define Counter Reducer for Zutron Source: https://context7.com/goosewobbler/zutron/llms.txt Define a Redux-style reducer for managing counter state. This reducer handles actions like increment, decrement, and set. ```typescript // src/features/counter/index.ts import type { Reducer } from 'zutron'; type CounterAction = | { type: 'COUNTER:INCREMENT' } | { type: 'COUNTER:DECREMENT' } | { type: 'COUNTER:SET'; payload: number }; export const counterReducer: Reducer = (state, action) => { switch (action.type) { case 'COUNTER:INCREMENT': return state + 1; case 'COUNTER:DECREMENT': return state - 1; case 'COUNTER:SET': return action.payload as number; default: return state; } }; ``` -------------------------------- ### Define Separate Action Handlers Source: https://context7.com/goosewobbler/zutron/llms.txt Define action handlers as functions that receive the store and manipulate state directly. Pass the handlers object to `mainZustandBridge` via the `handlers` option. This approach keeps action logic separate from the store definition. ```typescript // src/features/counter/index.ts import type { Store } from '../main/store.js'; export const handlers = (store: Store) => ({ 'COUNTER:INCREMENT': () => store.setState((state) => ({ counter: state.counter + 1 })), 'COUNTER:DECREMENT': () => store.setState((state) => ({ counter: state.counter - 1 })), 'COUNTER:SET': (value: number) => store.setState(() => ({ counter: value })), }); // src/features/index.ts import { handlers as counterHandlers } from './counter/index.js'; import type { Store } from '../main/store.js'; export type AppState = { counter: number }; export const actionHandlers = (store: Store, initialState: AppState) => ({ ...counterHandlers(store), 'STORE:RESET': () => store.setState(() => initialState), }); // src/main/index.ts import { createStore } from 'zustand/vanilla'; import { mainZustandBridge } from 'zutron/main'; import { actionHandlers, type AppState } from '../features/index.js'; const initialState: AppState = { counter: 0 }; const store = createStore()(() => initialState); const handlers = actionHandlers(store, initialState); const { unsubscribe } = mainZustandBridge(store, [mainWindow], { handlers }); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.