react-spa-template/docs/project-structure.md
2025-04-19 19:24:27 +02:00

3.7 KiB

Project Structure

This document outlines the structure of our application, which follows a domain-driven, Flux-style architecture using Effector for state management.

Directory Structure

src/
  app/                # Application entry point and configuration
    root.tsx          # Root component
    routes.tsx        # Route definitions
  domains/            # Feature domains
    <feature>/        # e.g., products, auth, orders
      model.ts        # Effector events, stores, effects
      view-model.ts   # React hooks for connecting to the model
      ui/             # React components
        index.ts      # Exports all components
        Component.tsx # Individual components
  shared/             # Shared code
    lib/              # Utilities, helpers, adapters
    ui/               # Reusable UI components
  tests/              # Test files

Domain Structure

Each domain follows a consistent structure:

model.ts

Contains all Effector-related code:

  • Events (actions)
  • Stores (state)
  • Effects (async operations)

Example:

// domains/products/model.ts
import { createStore, createEvent, createEffect } from 'effector';
import { ProductsViewsService } from '@lib/api/merchant/services/ProductsViewsService';
import type { productCompositeDto } from '@lib/api/merchant/models/productCompositeDto';

// Events
export const setProductsFilter = createEvent<Partial<productQueryRequestDto>>();

// Effects
export const fetchProductsFx = createEffect(async (filter: productQueryRequestDto) => {
  return await ProductsViewsService.postApiProductCompositeQueryByCanonical({
    requestBody: filter
  });
});

// Stores
export const $products = createStore<productCompositeDto[]>([])
  .on(fetchProductsFx.doneData, (_, payload) => Array.isArray(payload) ? payload : [payload]);

view-model.ts

Contains React hooks that connect to the Effector model:

// domains/products/view-model.ts
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/

Contains React components that use the view-model:

// domains/products/ui/ProductsList.tsx
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 <div>Loading...</div>;
  }
  
  return (
    <div className="grid grid-cols-3 gap-4">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
};

Shared Code

shared/lib/

Contains utilities, helpers, and adapters that are used across multiple domains.

shared/ui/

Contains reusable UI components that are not tied to any specific domain.

Tests

Tests are organized alongside the code they test, following the same structure.

Best Practices

  1. Keep domains isolated: Each domain should be self-contained and not depend on other domains.

  2. Use named exports: Avoid default exports to make refactoring easier.

  3. Keep files small: Each file should have a single responsibility.

  4. Use TypeScript: All files should be written in TypeScript for type safety.

  5. Follow Flux pattern: Maintain unidirectional data flow from events to stores to views.