# Architecture: Flux-style with Effector ## 1. Terminology & Core Concepts | Term | Definition | |------|------------| | SPA (Single-Page Application) | Entire UI is boot-strapped once in the browser; further navigation is handled client-side. | | Client-Side Rendering (CSR) | The browser runs JavaScript (React) that turns JSON/state into DOM; no HTML is streamed from the server after first load. | | Effector | A small TypeScript-first library that implements Flux-style one-way data-flow with four primitives (event, store, effect, domain). | | Flux-style architecture | Events mutate stores ➜ stores notify subscribed views; data always moves in one direction. | | Effector-based Flux architecture | Using Effector as the single source of truth for state inside a React SPA. | ## 2. Effector Primitives → Flux Mapping ``` createEvent() ≈ Action (create)Store() ≈ Store createEffect() ≈ Async side-effect handler Domains ≈ Namespace / module boundary View ≈ React component using `useStore` / `useUnit` ``` Flow diagram: ``` event ─┐ │ triggers store ──┤ update → React re-render │ effect ←┘ (async; dispatches success/fail events) ``` ## 3. Project Layout ``` src/ app/ root.tsx # , routing routes.tsx # Route definitions domains/ / model.ts # Effector events, stores, effects (pure, testable) view-model.ts # Thin hooks that adapt model to React ui/ # Dumb presentational components shared/ ui/ # Design-system atoms lib/ # Generic helpers tests/ # Test files ``` ## 4. Coding Guidelines | Guideline | Why it helps | |-----------|--------------| | TypeScript strict mode | Types give static guarantees for safe edits. | | Pure functions in model.ts | Deterministic behavior is easier to reason about. | | Named exports only | Eliminates default-export rename hazards. | | Small files, single responsibility | Enables chunked, parallel code transforms. | | Unit tests co-located with domain logic | Provide regression oracles for automated changes. | | Runtime schema checks (e.g., Zod) | Fail fast on unexpected data. | ## 5. Implementation Example ### model.ts ```typescript import { createStore, createEvent, createEffect } from 'effector'; import { ProductsViewsService } from '@lib/api/merchant/services/ProductsViewsService'; import type { productCompositeDto } from '@lib/api/merchant/models/productCompositeDto'; // Define events export const setProductsFilter = createEvent>(); // Define effects export const fetchProductsFx = createEffect(async (filter: productQueryRequestDto) => { return await ProductsViewsService.postApiProductCompositeQueryByCanonical({ requestBody: filter }); }); // Create stores export const $products = createStore([]) .on(fetchProductsFx.doneData, (_, payload) => Array.isArray(payload) ? payload : [payload]); ``` ### view-model.ts ```typescript import { useStore } from 'effector-react'; import { useCallback } from 'react'; import { $products, $productsLoading, setProductsFilter, fetchProductsFx } from './model'; export const useProducts = () => { const products = useStore($products); const isLoading = useStore($productsLoading); const updateFilter = useCallback((newFilter) => { setProductsFilter(newFilter); }, []); return { products, isLoading, updateFilter }; }; ``` ### UI Component ```typescript import React from 'react'; import { useProducts } from '../view-model'; import { ProductCard } from './ProductCard'; export const ProductsList: React.FC = () => { const { products, isLoading } = useProducts(); if (isLoading) { return Loading...; } return ( {products.map(product => ( ))} ); }; ```