# AGENTS.md — Dashfront Developer & Agent Guide > **Audience**: AI Coding Agents & Pair-Programmers working on `dashfront`. > **Workspace**: `/home/seb/src/dashfront` > **Last Updated**: 2026-09-21 --- ## Table of Contents 1. [System Overview & Ecosystem Map](#1-system-overview--ecosystem-map) 2. [Technology Stack & Key Libraries](#2-technology-stack--key-libraries) 3. [Directory Layout](#3-directory-layout) 4. [State Management Conventions](#4-state-management-conventions) 5. [Backend Communication & Authentication Rules](#5-backend-communication--authentication-rules) 6. [Route & Navigation Architecture](#6-route--navigation-architecture) 7. [Developer Workflows & Commands](#7-developer-workflows--commands) 8. [Critical Agent Rules & Common Pitfalls](#8-critical-agent-rules--common-pitfalls) 9. [Deep Dive: The Automats (`react-automat`)](#9-deep-dive-the-automats-react-automat) 10. [Deep Dive: Redux Toolkit Architecture](#10-deep-dive-redux-toolkit-architecture) 11. [`dashgui` Component Library Integration](#11-dashgui-component-library-integration) 12. [Application Views & Route Hierarchy](#12-application-views--route-hierarchy) 13. [Media, Images & Document Pipelines](#13-media-images--document-pipelines) 14. [Real-time SSE Signals & Invalidation](#14-real-time-sse-signals--invalidation) 15. [Developer Tooling & Debugging](#15-developer-tooling--debugging) 16. [Agent Cheatsheet & Code Recipes](#16-agent-cheatsheet--code-recipes) --- ## 1. System Overview & Ecosystem Map `dashfront` is the React web client and Progressive Web App (PWA) for the Dash ERP/POS system. It is part of a multi-repository workspace under `/home/seb/src/`: ``` /home/seb/src/ ├── dashfront/ # THIS REPO: Vite + React 19 web/PWA client (port 4209 HTTP, 4308 HTTPS) ├── dashback/ # Backend Express REST/SSE API server (port 4208) ├── dashdbschema/ # LibDashStore (MSSQL data layer, migrations, schema graph, API docs) ├── dashgui/ # Reusable custom UI component library (file:../dashgui) └── automat/ # react-automat state management library (file:../automat or npm) ``` ### End-to-End Data & Execution Flow ```yaml # Machine-readable execution flow: flow: - step: 1_client component: dashfront runtime: React 19 (PureComponent & Function Components) state_layers: automats: [artikelAutomat, deliveryEditorAutomat, authAutomat, supplierAutomat, navigationAutomat, userSettingsAutomat] redux_slices: [company, category, brand, unit, posterminal] transport: src/api (http(), ApiClient) egress: protocol: HTTP (fetch with credentials: "include") & SSE (/api/events) proxy: Vite dev server (/api -> http://localhost:4208) target: dashback - step: 2_api_gateway component: dashback runtime: Express 5 API server (src/api/routes/*) security: auth_middleware: Cookie auth middleware (validates JWT in HTTP-only "token" cookie) context_injection: user_auth_id: req.user.googleAuthId tenant_company_id: req.selectedCompanyId events: Dispatches real-time SSE mutation events to subscribed clients via /api/events egress: interface: In-process method calls (store..()) target: dashdbschema - step: 3_data_layer component: dashdbschema runtime: LibDashStore (src/store/*) storage: MSSQL connection pool, migrations, and SQL transactions ``` --- ## 2. Technology Stack & Key Libraries | Technology | Version / Spec | Notes | |---|---|---| | **Build & Dev** | Vite 8 (`type: module`) | Dev server on `4209`, HTTPS on `4308`, proxies `/api` → `http://localhost:4208` | | **Framework** | React 19 (`react`, `react-dom`) | Uses `PureComponent` (with Automats & dashgui) and modern function components | | **Routing** | `react-router-dom` v7 | Configured in [src/appRouter.jsx](file:///home/seb/src/dashfront/src/appRouter.jsx) with HMR router preservation | | **UI & Theming** | MUI 9 (`@mui/material`, Emotion) | Custom theme in `src/theme/`, base background `#c5ddd8` in `src/colors.js` | | **Shared GUI** | `dashgui` (`file:../dashgui`) | `PanelTile`, `RecordList`, `Tile`, `SingleLineTextEdit`, `ElevatedPill` | | **State Management** | `react-automat` + Redux Toolkit | Automats for reactive domain models; Redux for category tree & session bootstrap | | **Notifications** | `notistack` | `useSnackbar()` / `` in [src/main.jsx](file:///home/seb/src/dashfront/src/main.jsx) | | **Media & Images** | `@jsquash/avif`, `@jsquash/resize`, `thumbhash` | Client-side AVIF encoding and instant thumbnail hash generation | | **Documents** | `pdfconvert-wasm` | In-browser PDF processing for supplier delivery invoices & receipts | | **Drag & Drop** | `@shopify/draggable`, `sortablejs` | Category re-ordering, article assignments, nested tree nodes | --- ## 3. Directory Layout ``` src/ ├── api/ # Backend communication layer │ ├── index.js # Canonical API module exports (article, category, delivery, etc.) │ ├── ApiClient.js # Object-oriented API client (this.article, this.delivery, etc.) │ ├── StorageManager.js # Client-side cache / local storage management │ └── storageManagerContext.js # React context provider for StorageManager ├── automats/ # react-automat stores with fine-grained subscriptions │ ├── artikelAutomat.js # Article cache, deduplicated loads, and reactive single-article state │ ├── authAutomat.js # Authentication state, company list, company selection │ ├── deliveryEditorAutomat.js # Multi-line delivery receipt / invoice editor & draft state │ ├── navigationAutomat.js # Remembers active paths (last delivery path, last article SKU) │ ├── supplierAutomat.js # Supplier list and supplier-article catalogs │ ├── userSettingsAutomat.js # User preferences (viewer positions, layout settings) │ └── index.js # Barrel exports for all automats and helper selectors ├── components/ # Shared UI components │ ├── AppShell.jsx # Main application frame (top bar, sidebar, nav buttons) │ ├── SectionLayout.jsx # Secondary navigation header for sections (/artikel, /lieferung...) │ ├── AuthBootstrap.jsx # Session bootstrapper, Google One Tap, company setup gate │ ├── LoginPage.jsx # Google One Tap / credentials login interface │ ├── UserMenu.jsx # User avatar, company switcher dropdown, logout action │ ├── BackendError.jsx # Graceful error banner for backend communication failures │ ├── article/ # Article tiles, editors, dialogs, photos, price tiers │ ├── category/ # Category navigation, dialogs, and picker components │ ├── manifest/ # Manifest / delivery line inspection widgets │ ├── settings/ # Terminal, unit, and brand settings dialogs │ └── tiles/ # Tile components extending dashgui base Tile ├── devtools/ # Dev-only debug tools │ └── AutomatInspector.jsx # Docked overlay inspecting all registered window.__AUTOMATS__ ├── pages/ # Top-level view routes │ ├── artikel/ # Article index, category browser, warehouse, SKU detail editor │ ├── lieferung/ # Delivery list, new delivery editor, supplier manager, edit page │ ├── auftrag/ # Quotes (Angebote) and orders (Bestellungen) │ ├── kunde/ # Customer list, new customer, customer groups, edit page │ └── infos/ # System and company information page ├── store/ # Redux Toolkit store (legacy & tree slice) │ ├── index.js # configureStore with dev logger & listener middleware │ ├── authSlice.js # Redux auth state (session bootstrap, company selection) │ ├── companySlice.js # Redux company details (tax rates, rootCategoryId) │ ├── categorySlice.js # Redux category tree, breadcrumbs, category articles │ ├── brandSlice.js # Redux brand list and lookup │ ├── posterminalSlice.js # Redux POS terminals configuration │ └── unitSlice.js # Redux unit of measure definitions ├── theme/ # MUI theme definitions (light/dark palette, typography, components) ├── utils/ # Helpers (apiShape.js, number formatters, notify.js, etc.) ├── appRouter.jsx # react-router-dom route definitions ├── nav.js # Navigation items (NAV_ITEMS, SUB_NAV, badges, active path matchers) ├── colors.js # Shared color constants (MAIN_BACKGROUND) ├── main.jsx # React root mount, providers, dev SW unregistration └── App.jsx # Main router provider wrapper ``` --- ## 4. State Management Conventions In `dashfront`, two state management solutions coexist harmoniously: ### A. `react-automat` (Recommended for Domain Models & Frequent Updates) - **Skill reference**: Read [.agents/skills/automat/SKILL.md](file:///home/seb/src/dashfront/.agents/skills/automat/SKILL.md). - Lives outside the React lifecycle in module scope (`src/automats/*.js`). - Designed for React `PureComponent` to bypass unnecessary React reconciliations. - **Pattern**: ```jsx import { PureComponent } from 'react'; import { artikelAutomat, createArticleSelector, getArticle } from '../automats'; class ArticleView extends PureComponent { constructor(props) { super(props); // 1. Synchronous read in constructor — zero blank flash! this.state = { article: getArticle(props.sku), }; } componentDidMount() { // 2. Subscribe with fine-grained selector this.unsub = artikelAutomat.subscribe(this, (state) => ({ article: createArticleSelector(this.props.sku)(state), })); } componentWillUnmount() { // 3. Always unsubscribe this.unsub?.(); } render() { const { article } = this.state; if (!article) return
Lädt...
; return
{article.name}
; } } ``` ### B. Redux Toolkit (`src/store/`) - Used for static/relational trees: `company`, hierarchical `category` tree, `brand`, `posterminal`, and `unit`. - Uses `connect(mapStateToProps, mapDispatchToProps)` to connect to `PureComponent` classes or functional hooks where appropriate. - Coordinated via `createListenerMiddleware`: when `authAutomat` emits an auth change (`auth/sessionEstablished` or `auth/companyChanged`), listeners automatically dispatch loads for company info, brands, POS terminals, and units. --- ## 5. Backend Communication & Authentication Rules ### Authentication & Cookies 1. **HTTP-only cookie**: The session token is stored in an HTTP-only cookie (`token`). 2. **`credentials: 'include'`**: Every `fetch` request MUST set `credentials: 'include'`. The `http()` helper in [src/api/index.js](file:///home/seb/src/dashfront/src/api/index.js) does this automatically. 3. **Never pass `googleAuthId`**: The frontend NEVER passes `googleAuthId` in request bodies or query parameters. The backend middleware extracts the authenticated user directly from the cookie session! 4. **Session Company**: Multi-tenancy is scoped to the user's selected company. When a user switches companies (`PUT /api/auth/selected-company`), the backend updates the cookie session. All subsequent calls operate on that company. ### API Entry Point Import API namespaces from `src/api`: ```js import { article, category, delivery, supplier, entity, company } from '../api'; // Examples: const { article: data } = await article.getBySku('SKU-1001'); const { deliveries } = await delivery.list(); const { suppliers } = await supplier.list(); ``` --- ## 6. Route & Navigation Architecture - Main navigation sections are defined in [src/nav.js](file:///home/seb/src/dashfront/src/nav.js): - `/artikel` — Articles overview, SKU editor, Warehouse stock - `/kategorien/*` — Drag-and-drop category tree - `/lieferung` — Inbound deliveries, New delivery editor, Suppliers - `/auftrag` — Quotes (`/auftrag`) and Orders (`/auftrag/bestellung`) - `/kunde` — Customers, Customer groups - `/infos` — System settings & company info - **Router Singleton**: - Defined in [src/appRouter.jsx](file:///home/seb/src/dashfront/src/appRouter.jsx). - Preserved across Vite HMR via `import.meta.hot.data.router` to prevent mid-session resets when hot-reloading code. - **Dynamic Badges**: - `DeliveryNavIcon` and `DeliveryNeuIcon` in `src/nav.js` subscribe to `deliveryEditorAutomat` to show live uncommitted draft line badges in real time. --- ## 7. Developer Workflows & Commands Run commands from the repository root `/home/seb/src/dashfront`: ```bash # Start Vite development server (proxies /api -> http://localhost:4208) npm run dev # Start development server with local HTTPS (uses certs/key.pem and certs/cert.pem) npm run dev:https # Run full PWA production build + preview (to test service worker / offline caching) npm run dev:pwa # Generate local self-signed TLS certificates npm run cert # Production build npm run build # Preview production build npm run preview ``` > [!NOTE] > When testing camera-based barcode scanning or Progressive Web App features on mobile devices or local network IP addresses, start the server using `npm run dev:https`. Web browsers require a secure context (HTTPS) to grant access to media devices and service workers. --- ## 8. Critical Agent Rules & Common Pitfalls 1. **Do not create dummy/mock backends**: `dashback` runs locally at `http://localhost:4208`. All `/api/*` routes are handled by it. 2. **Keep `dashgui` links working**: `dashgui` is imported via `file:../dashgui`. Vite has deduplication configured (`dedupe: ['react', 'react-dom', ...]` in `vite.config.js`) to avoid dual-instance React conflicts. 3. **Article Deletion Restriction**: An article CANNOT be deleted if it has child variants (`is_father: 1` or `childCount > 0`). The backend returns HTTP 400. 4. **Prices and Multipliers**: - Article prices are tiered by `minmultiplier` and `customergroup_id`. - Prices sent to the backend are net prices (`net`). 5. **Binary / Image Handling**: - Image uploads convert source images into AVIF variants client-side. - Frontend components use `thumbhash` for instant blur placeholders while loading full images via `/api/image/{fullHash}` or `/api/thumb/{fullHash}`. 6. **No Hooks in PureComponents**: Never call React hooks (`useState`, `useSelector`, `useEffect`) inside `PureComponent` classes. Automats and `connect()` provide direct reactive subscription without hooks. 7. **Always Unsubscribe**: Every `automat.subscribe(this, ...)` in `componentDidMount()` must have its cleanup callback called in `componentWillUnmount()`. --- ## 9. Deep Dive: The Automats (`react-automat`) All domain Automats are located in [src/automats/](file:///home/seb/src/dashfront/src/automats/). Each automat exposes an observable state container with explicit action methods: ### 1. `artikelAutomat` ([src/automats/artikelAutomat.js](file:///home/seb/src/dashfront/src/automats/artikelAutomat.js)) - **Role**: Cache and reactive distributor for article detail payloads (`GET /api/article/:sku`). - **Inflight Deduplication**: Uses an internal `Map>` so concurrent UI tiles or navigation transitions requesting the same SKU share a single HTTP call. - **Key Actions**: - `loadArticle(sku)`: Fetches if missing, resolves immediately if cached. - `reloadArticle(sku)`: Silently re-fetches to update cache (used on SSE mutation events). - `setArticle(article)`: Updates cache after mutation responses. - `setArticleName(sku, name)`: Optimistic immediate rename. - `clearArticle(sku)` / `clear()`: Invalidates cache. - **Helper Selectors**: `getArticle(sku)`, `hasArticle(sku)`, `createArticleSelector(sku)`, `selectArticle(sku)`. ### 2. `authAutomat` ([src/automats/authAutomat.js](file:///home/seb/src/dashfront/src/automats/authAutomat.js)) - **Role**: Manages user authentication lifecycle and multi-tenant company selection. - **State**: `{ user: AuthUser | null, googleClientId: string | null, status: 'loading' | 'anon' | 'authed' }`. - **Bridge to Redux**: Calls `emit(action)` registered via `setAuthEventDispatcher(store.dispatch)`. Emits: - `auth/sessionEstablished` - `auth/companyChanged` - `auth/loggedOut` - **Actions**: `bootstrapSession(session)`, `switchCompany(companyId)`, `logout()`. ### 3. `deliveryEditorAutomat` ([src/automats/deliveryEditorAutomat.js](file:///home/seb/src/dashfront/src/automats/deliveryEditorAutomat.js)) - **Role**: State engine for the multi-line inbound delivery receiver and invoice reconciler ([src/pages/lieferung/neu.jsx](file:///home/seb/src/dashfront/src/pages/lieferung/neu.jsx)). - **Features**: - Line items editor (`lines`, `createEmptyLineitem`). - Supplier article auto-matching via `buildSupplierArticlesMap(articles)` indexing by supplier SKU, internal SKU, and EANs. - PDF/Receipt attachments parsing with OCR integration. - Reactive draft indicators (provides draft counts for badges in `src/nav.js`). ### 4. `supplierAutomat` ([src/automats/supplierAutomat.js](file:///home/seb/src/dashfront/src/automats/supplierAutomat.js)) - **Role**: Caches supplier directory list and maps articles to their corresponding supplier catalogs (`GET /api/supplier`). ### 5. `navigationAutomat` ([src/automats/navigationAutomat.js](file:///home/seb/src/dashfront/src/automats/navigationAutomat.js)) - **Role**: Remembers last active subpaths and last visited article SKU. When a user navigates between the main sidebar sections (e.g. from `/lieferung` to `/artikel` and back), it restores the exact active SKU or delivery document instead of resetting to the root list. ### 6. `userSettingsAutomat` ([src/automats/userSettingsAutomat.js](file:///home/seb/src/dashfront/src/automats/userSettingsAutomat.js)) - **Role**: Stores client-side user layout preferences such as viewer panel coordinates and drawer positions. --- ## 10. Deep Dive: Redux Toolkit Architecture Redux Toolkit manages static, relational, and configuration data in [src/store/](file:///home/seb/src/dashfront/src/store/): ### Slices 1. **`categorySlice`**: Hierarchical category tree structure, category breadcrumbs, article-category memberships, category reordering (`moveCategory`, `reorderCategories`). 2. **`companySlice`**: Active company configuration, tax rates (standard, reduced), root category ID, company legal details. 3. **`brandSlice`**: Catalog brands and manufacturer list. 4. **`posterminalSlice`**: Registered point-of-sale hardware terminals. 5. **`unitSlice`**: Allowed units of measure (Stück, kg, Liter, etc.). ### Automatic Bootstrap with Listener Middleware In [src/store/index.js](file:///home/seb/src/dashfront/src/store/index.js), `createListenerMiddleware` coordinates data loading automatically when auth events fire: ```js companyLoadListener(listenerMiddleware); unitLoadListener(listenerMiddleware); brandLoadListener(listenerMiddleware); posterminalLoadListener(listenerMiddleware); ``` When `auth/sessionEstablished` or `auth/companyChanged` occurs, these listeners trigger thunks to reload company tax settings, brands, units, and POS terminals without boilerplate in React views. ### Redux Dev Logger In development mode (`import.meta.env.DEV`), `reduxDevLogger` intercepts actions from `auth/`, `company/`, `category/`, `brand/`, `posterminal/`, and `unit/`, logging structured before/after counts to the browser console. --- ## 11. `dashgui` Component Library Integration `dashgui` is a shared, high-performance UI library linked from `file:../dashgui`: ### Core Components - **`PanelTile`**: Structured card container featuring standard header styling, action slots, collapsible state, and clean borders. - **`RecordList`**: High-performance list component optimized for large dataset rendering with custom row click, hover, and selection handlers. - **`Tile`**: Base container element for building responsive dashboard grids. - **`SingleLineTextEdit`**: Inline editable text field with debounced autosave, blur commit, and escape rollback. - **`ElevatedPill`**: Compact visual badges for status, tags, and category chips. ### React 19 & Deduplication Warning Because `dashgui` is symlinked from `../dashgui`, it has its own `node_modules`. To prevent duplicate React, Emotion, or MUI instances from causing runtime context errors (`Cannot read properties of null (reading 'useContext')`), [vite.config.js](file:///home/seb/src/dashfront/vite.config.js) enforces `dedupe`: ```js resolve: { dedupe: [ 'react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'react-router', 'react-router-dom', '@emotion/react', '@emotion/styled', '@mui/material', '@mui/icons-material' ] } ``` --- ## 12. Application Views & Route Hierarchy The application routing is defined in [src/appRouter.jsx](file:///home/seb/src/dashfront/src/appRouter.jsx) and styled with [src/components/AppShell.jsx](file:///home/seb/src/dashfront/src/components/AppShell.jsx): ``` / ├── /artikel │ ├── index → ArtikelUebersichtPage (Search & filter grid) │ ├── /kategorien/* → ArtikelKategorienPage (Interactive category tree) │ ├── /lager → ArtikelLagerPage (Warehouse stock overview) │ └── /:sku/:tab? → ArtikelSkuPage (SKU detail: info, photos, prices, suppliers, warehouse) ├── /lieferung │ ├── index → LieferungListePage (Inbound delivery list) │ ├── /neu → LieferungNeuPage (Receipt editor & OCR reconciliation) │ ├── /lieferanten → LieferungLieferantenPage (Supplier directory) │ └── /:deliveryId → LieferungEditPage (Delivery inspection & editing) ├── /auftrag │ ├── index → AuftragAngebotPage (Quotes / Angebote) │ ├── /neu → AuftragAngebotNeuPage (Create quote) │ ├── /:quoteId → AuftragAngebotEditPage (Edit quote) │ ├── /bestellung → AuftragBestellungPage (Orders / Bestellungen) │ ├── /bestellung/neu → AuftragBestellungNeuPage (Create order) │ └── /bestellung/:id → AuftragBestellungEditPage (Edit order) ├── /kunde │ ├── index → KundeListePage (Customer directory) │ ├── /neu → KundeNeuPage (Create customer) │ ├── /gruppen → KundeGruppenPage (Customer groups & price tiers) │ └── /:entityId → KundeEditPage (Customer profile & address editor) └── /infos → InfosPage (Company information & POS setup) ``` --- ## 13. Media, Images & Document Pipelines ### Client-Side AVIF Conversion To minimize backend CPU usage and reduce bandwidth: - When a user selects or drops an article photo, the frontend processes the image in-browser using `@jsquash/avif` and `@jsquash/resize`. - Images are scaled to target variant dimensions and compressed into AVIF format before transmission to `/api/article/:sku/photo`. ### ThumbHash Placeholders - For instant visual feedback without layout shift, article photos generate a 25-byte binary hash using `thumbhash`. - [src/components/article/ThumbHashImage.jsx](file:///home/seb/src/dashfront/src/components/article/ThumbHashImage.jsx) renders the blur preview instantly while the full image loads asynchronously from `/api/thumb/{hash}` or `/api/image/{hash}`. ### PDF & Document Ingestion - [src/pages/lieferung/neu.jsx](file:///home/seb/src/dashfront/src/pages/lieferung/neu.jsx) utilizes `pdfconvert-wasm` to process supplier PDF delivery notes and invoices directly inside the browser. - Extracted pages are rendered to canvas, displayed for line-by-line inspection, and passed to OCR recognition to populate delivery line items. --- ## 14. Real-time SSE Signals & Invalidation The frontend connects to the backend Server-Sent Events stream at `/api/events`: - **Architecture**: When mutations occur on `dashback` (whether triggered by this client, another terminal, or the POS), SSE events are broadcast with payload information (e.g. `{ type: 'article-update', sku: '1001' }`). - **Reactive Cache Refresh**: - `artikelAutomat.actions.reloadArticle(sku)` re-fetches the updated article silently, updating subscribers without flicker. - Category updates trigger Redux category slice refreshes. - Delivery and order updates trigger automatic list reloads. --- ## 15. Developer Tooling & Debugging ### AutomatInspector ([src/devtools/AutomatInspector.jsx](file:///home/seb/src/dashfront/src/devtools/AutomatInspector.jsx)) - Docked overlay on the right side of the screen, active only in development mode (`import.meta.env.DEV`). - Auto-discovers all running automats registered in `window.__AUTOMATS__`. - Allows developers and agents to: - Inspect full state JSON trees in real time. - View subscriber counts and registered actions. - Trigger actions and test state changes without code modifications. ### Vite API Proxy Logging [vite.config.js](file:///home/seb/src/dashfront/vite.config.js) includes automatic proxy logging for all `/api` requests and responses. HTTP method, path, request payloads, response statuses, and timing are formatted directly in the Vite terminal. ### TLS / HTTPS Development For testing hardware barcode scanners, camera video feeds, and PWA offline modes: ```bash npm run cert # Generates local certs in ./certs/ npm run dev:https # Runs Vite HTTPS server on port 4308 ``` --- ## 16. Agent Cheatsheet & Code Recipes ### Recipe 1: Consuming an Automat in a React PureComponent ```jsx import { PureComponent } from 'react'; import { artikelAutomat, createArticleSelector, getArticle } from '../automats'; export class ArticleTitle extends PureComponent { constructor(props) { super(props); // 1. Read synchronously to avoid blank flash this.state = { article: getArticle(props.sku), }; } componentDidMount() { // 2. Subscribe with fine-grained selector this.unsub = artikelAutomat.subscribe(this, (state) => ({ article: createArticleSelector(this.props.sku)(state), })); } componentDidUpdate(prevProps) { // 3. Handle prop changes cleanly if (prevProps.sku !== this.props.sku) { this.setState({ article: getArticle(this.props.sku) }); this.unsub?.(); this.unsub = artikelAutomat.subscribe(this, (state) => ({ article: createArticleSelector(this.props.sku)(state), })); } } componentWillUnmount() { // 4. Always cleanup this.unsub?.(); } render() { const { article } = this.state; return

{article?.name ?? 'Lädt...'}

; } } ``` ### Recipe 2: Calling the API and Notifying Errors ```jsx import { article } from '../api'; import { enqueueSnackbar } from 'notistack'; import { notifyError } from '../utils/notify'; import { artikelAutomat } from '../automats'; async function handleSavePrice(sku, netPrice) { try { const res = await article.updatePrice(sku, { net: netPrice }); artikelAutomat.actions.setArticle(res.article); enqueueSnackbar('Preis erfolgreich aktualisiert', { variant: 'success' }); } catch (err) { notifyError(err); } } ``` ### Recipe 3: Calculating Gross Prices with Company Tax Rates ```jsx // Prices in the database and API are NET prices. // Gross prices shown to end users must apply company tax rates from Redux companySlice: export function calculateGrossPrice(netPrice, taxRatePercent) { const net = Number(netPrice) || 0; const rate = Number(taxRatePercent) || 0; return Math.round(net * (1 + rate / 100) * 100) / 100; } ```