Skip to content

Frontend Architecture Refactoring Plan #358

Description

@SagiEv

Frontend Architecture Refactoring Plan

The purpose of this plan is to outline the architectural code smells present in the current React Native (Expo) frontend and propose a strategy to refactor and clean up the code.

1. Circular Dependencies and Inline Requires

Problem:
Core modules like apiFetch.ts, authStore.ts, and App.tsx are using inline require() calls inside functions (e.g., const { useAuthStore } = require("@/stores/authStore");) to break circular dependencies. This is a severe anti-pattern that bypasses static analysis, makes testing difficult, and indicates tightly coupled components.

Proposed Solution (Inversion of Control):

  • Remove all inline require() calls.
  • Break the dependency between apiFetch (HTTP client) and authStore (State management) by using a callback/interceptor pattern.
    • E.g., Expose a setAuthInterceptor(onUnauthorized: () => void, onRefresh: () => Promise<boolean>) method in apiFetch.ts.
    • In App.tsx or an initialization script, link the store actions to the interceptor.

2. Fat UI Components & Mixed Concerns

Problem:
UI components, particularly LoginScreen.tsx (and likely others like ProfileScreen.tsx and RegisterForm.tsx), contain heavy business logic. LoginScreen.tsx is manually constructing API requests (fetch(...)), parsing responses, handling Stardbi mappings, and orchestrating Zustand state updates.

Proposed Solution (Thin Components):

  • Extract all API interaction and complex business logic into dedicated services (e.g., services/authService.ts) or encapsulate them fully within the Zustand stores.
  • LoginScreen.tsx should only handle local form state (username, password), validation, and invoke a single function like authStore.login(username, password).

3. Monolithic React Query File

Problem:
app/api/queries.ts is a massive monolithic file containing every React Query hook for the entire application (Tasks, Gamification, Users, Admin, etc.). This makes it hard to maintain and prone to merge conflicts.

Proposed Solution (Feature-based Modularization):

  • Break down queries.ts into a queries/ directory grouped by domain context:
    • queries/authQueries.ts
    • queries/taskQueries.ts
    • queries/gamificationQueries.ts
    • queries/userQueries.ts
    • queries/adminQueries.ts
  • Keep a centralized queryKeys.ts factory if necessary, or keep keys local to their domain files.

4. Inconsistent API Usage

Problem:
While apiFetch.ts provides an authenticated fetch wrapper, there are scattered raw fetch() calls in the codebase (e.g., authStore.logout uses raw fetch and constructs Authorization headers manually; LoginScreen.tsx uses raw fetch for Stardbi login).

Proposed Solution:

  • Strictly enforce the use of apiFetch for all backend interactions.
  • If unauthenticated requests are needed, provide a clear standard (e.g., apiFetch(..., { requireAuth: false })) rather than falling back to standard fetch.

5. Architectural Recommendations (Addressing Open Questions)

Based on the nature of SwipeLab—particularly its moderate-to-high complexity (researcher vs. user modes, gamification, swiping, superadmin flows) and its backend architecture—here are my recommendations for the refactoring direction:

Recommendation A: Adopt a Feature-Based (Domain-Driven) Folder Structure

Currently, the frontend separates files by technical layer (app/components, app/screens, app/stores, app/api). I recommend moving to a Feature-Based (Modular) structure.

Trade-offs:

  • Technical Layer (Current): Easier for small apps, but as SwipeLab grows, modifying the "Task" feature requires jumping between screens/TaskScreen, stores/taskStore, api/queries.ts, and components/TaskCard. This reduces cohesion and increases cognitive load.
  • Feature-Based (Recommended): Groups everything by domain (e.g., app/features/auth, app/features/tasks, app/features/gamification).
    • Pros: High cohesion. An engineer working on authentication only needs to look in features/auth. This also perfectly mirrors your backend's Modular Hexagonal/DDD structure, creating a unified mental model across the stack.
    • Cons: Requires a significant initial file migration. You will also need a features/core or features/shared folder for global UI components (like Buttons) or global hooks.

Recommendation B: Testing Strategy (Test-Driven Refactoring)

The SwipeLab rules strictly enforce test coverage for modifications. Since this refactor touches critical infrastructure (Auth, API fetching), regressions are a high risk.

Trade-offs:

  • Relying solely on E2E (Playwright): You already have Playwright tests (e.g., settings.spec.ts). While excellent for ensuring the app works holistically, E2E tests are slow and can be brittle during structural refactors.
  • Adding Unit Tests (Jest + React Native Testing Library): Fast and isolated. Testing the new authStore or apiFetch interceptors in isolation guarantees that the foundational logic is sound before the UI even renders.

Recommended Approach:

  1. Write Unit Tests First: Before touching authStore.ts or apiFetch.ts, ensure unit tests exist for them. When we break the circular dependencies, these tests will turn green to confirm success.
  2. Lean on Playwright for UI: Let the existing E2E tests serve as our safety net to ensure we didn't break routing or screen rendering when moving components into the feature-based structure.

Proposed File Changes

Stores & Services

[MODIFY] authStore.ts

[NEW] authService.ts

API Layer

[MODIFY] apiFetch.ts

[DELETE] queries.ts

[NEW] queries/taskQueries.ts

[NEW] queries/userQueries.ts

[NEW] queries/gamificationQueries.ts

Screens

[MODIFY] LoginScreen.tsx

[MODIFY] ProfileScreen.tsx

[MODIFY] App.tsx

Verification Plan

Automated Tests

  • Run npm run test or Playwright end-to-end tests (npm run test:e2e) to verify that the core flows (login, tasks, profile) remain intact after the refactor.

Manual Verification

  • Manually test local and external (researcher) login flows.
  • Verify that session expiration and token refresh still work correctly without circular dependency issues.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions