# `artikelAutomat` > **Source File**: [`src/automats/artikelAutomat.js`](file:///home/seb/src/dashfront/src/automats/artikelAutomat.js) > **Registration Key**: `'artikel'` (`Automat.get('artikel')` / `window.__AUTOMATS__.get('artikel')`) --- ## 1. Overview & Purpose `artikelAutomat` serves as the centralized cache and reactive distributor for individual article detail payloads (`GET /api/article/:sku`). ### Core Responsibilities - **In-flight Deduplication**: Multiple components or route guards requesting the same SKU concurrently share a single HTTP request via an internal `Map>`. - **Zero-Flicker Navigation**: Navigating between tabs or returning to previously viewed articles reuses the cached detail object instantly without round-trips. - **Optimistic Updates**: Immediate UI updates (such as renaming an article via `setArticleName`) without awaiting network round-trips. - **Granular Subscriptions**: Components can subscribe to an individual SKU via `createArticleSelector(sku)` rather than re-rendering on every cache change. --- ## 2. State Structure ```ts interface ArtikelState { /** * Cached article detail payloads keyed by SKU string. * A missing key indicates the article is not yet loaded into memory. * Values include full article details: prices, photos, eans, brand, unit, * categoryIds, manifests, child variants, etc. */ bySku: Record; } ``` ### Initial State ```js const initialState = { bySku: {}, }; ``` --- ## 3. Configuration & Options ```js new Automat(initialState, actions, { name: 'artikel' }); ``` - **`name`**: `'artikel'` — registers the automat globally. - **`persist`**: `false` (default in-memory window lifecycle). --- ## 4. Actions & Methods Access actions via `artikelAutomat.actions.()`. ### `loadArticle(sku: string): Promise` Loads an article detail into the cache. If already present in `bySku`, returns the cached article immediately. If an HTTP request for this SKU is currently in-flight, it returns that shared promise. Otherwise, dispatches `GET /api/article/:sku`, normalizes the response, stores it in `bySku`, and cleans up the in-flight map. ### `reloadArticle(sku: string): Promise` Bypasses the cached copy and silently fetches a fresh detail payload from the backend. The existing cache entry is preserved until the response arrives. Typically used during SSE mutation events or user pull-to-refresh. ### `setArticle(article: object): object` Puts an article detail object directly into `bySku[article.sku]`. Requires `article` to be an object with a defined `sku`. Used after create/update API mutations so that all subscribers immediately observe the latest data. ### `setArticleName(sku: string, name: string): object | null` Optimistically updates the `name` property of a cached article without making an API call. Returns the updated article object or `null` if the SKU was not found in cache. ### `clearArticle(sku: string): void` Removes a specific SKU from `bySku`. Useful after deleting an article or when forcing a complete refetch on next access. ### `clear(): void` Resets the entire `bySku` dictionary to empty (`{}`). --- ## 5. Selectors & Helper Functions Exported alongside `artikelAutomat`: ### `getArticle(sku: string): object | null` Synchronous read from `artikelAutomat.state.bySku[sku]`. Returns `null` if not found or if `sku` is nullish. ```js const article = getArticle('10042'); ``` ### `hasArticle(sku: string): boolean` Returns `true` if `sku` is already loaded in `bySku`. ```js if (!hasArticle('10042')) { void artikelAutomat.actions.loadArticle('10042'); } ``` ### `createArticleSelector(sku: string): (state: ArtikelState) => object | null` Produces a memoized selector function for a specific SKU. Pass this to `artikelAutomat.subscribe(target, selector)` so your component only re-renders when this specific article's data changes. ### `selectArticle(sku: string): ScopedSlice` Returns a scoped slice for the given SKU (`artikelAutomat.select(createArticleSelector(sku))`). --- ## 6. Usage Examples ### A. React `PureComponent` Detail Header ```jsx import { PureComponent } from 'react'; import PropTypes from 'prop-types'; import artikelAutomat, { getArticle, createArticleSelector } from '../automats/artikelAutomat'; export class ArticleHeader extends PureComponent { static propTypes = { sku: PropTypes.string.isRequired, }; constructor(props) { super(props); // Synchronous initial state read prevents layout shift / loading spinner flicker this.state = { article: getArticle(props.sku), }; } componentDidMount() { this.subscribe(this.props.sku); if (!this.state.article) { artikelAutomat.actions.loadArticle(this.props.sku).catch(console.error); } } componentDidUpdate(prevProps) { if (prevProps.sku !== this.props.sku) { this.subscribe(this.props.sku); if (!hasArticle(this.props.sku)) { artikelAutomat.actions.loadArticle(this.props.sku).catch(console.error); } } } componentWillUnmount() { this.unsub?.(); } subscribe(sku) { this.unsub?.(); this.setState({ article: getArticle(sku) }); this.unsub = artikelAutomat.subscribe(this, createArticleSelector(sku)); } render() { const { article } = this.state; if (!article) return
Artikel wird geladen...
; return (

{article.name}

SKU: {article.sku}
); } } ``` ### B. Inline Optimistic Rename ```js import { artikelAutomat } from '../automats'; import { article as articleApi } from '../api'; async function updateName(sku, newName) { // 1. Optimistic update in UI cache artikelAutomat.actions.setArticleName(sku, newName); try { // 2. Persist to backend const res = await articleApi.update(sku, { name: newName }); // 3. Re-set authoritative response artikelAutomat.actions.setArticle(res.article); } catch (err) { // 4. Rollback on failure await artikelAutomat.actions.reloadArticle(sku); throw err; } } ``` --- ## 7. Best Practices & Gotchas 1. **Always clean up subscriptions**: Save `this.unsub = artikelAutomat.subscribe(...)` and call `this.unsub()` in `componentWillUnmount`. 2. **Use `createArticleSelector`**: Never subscribe an article view component with the raw `artikelAutomat.subscribe(this)` without a selector; otherwise, any other article loaded or modified anywhere in the app will trigger an unneeded re-render. 3. **Do not mutate directly**: Never write `artikelAutomat.state.bySku[sku].name = 'foo'`. Always use `setArticle` or `setArticleName`.