---
name: react-native-architecture
description: >-
  Structure and review React Native project architecture. Feature-based
  folders, N-layered separation, dependency direction, and import
  boundaries. Use when creating features, moving files, or reviewing
  structure.
argument-hint: "[feature name]"
license: MIT
metadata:
  author: John Felix Lim
  version: "1.0.0"
---
# Project Architecture

Apply these rules when creating a new feature, relocating files, or reviewing a pull request that changes project structure. The dependency direction rule is not negotiable: a violation is a defect, not a style preference.

## Rules

1. **Layer by dependency direction, not by file type** — UI depends on domain, domain depends on data — never the reverse. A `components/` folder holding every component in the app tells you nothing about what depends on what. Layers do, and they make the dangerous imports visible in review.
2. **Group by feature, colocate ruthlessly** — A feature owns its screens, hooks, state, and types in one folder. If deleting a feature means touching eight top-level directories, the structure is working against you. Only genuinely shared code graduates to `shared/`.
3. **The domain layer must not import from React Native** — Business rules that import `Dimensions` or `Platform` cannot be unit tested without a native environment, and cannot be reused if you ever add a web target. Keep the domain layer pure TypeScript — this is the single highest-leverage rule in the list.
4. **One public entry point per feature** — Export a feature's surface from its `index.ts`. Deep imports into `features/checkout/hooks/internal/useDraft` are how a refactor turns into a week. The barrel file is the contract.
5. **Enforce boundaries with tooling, not good intentions** — Conventions decay the moment a deadline appears. An ESLint `no-restricted-imports` rule that fails the build is the only version of this that survives contact with a real team.

## Patterns

### Folder structure

**Avoid** — Type-based folders. Adding one feature touches five directories, and nothing tells you which code belongs together.

```bash
src/
  components/     # 200 files, no grouping
  screens/        # every screen in the app
  hooks/          # every hook in the app
  utils/          # the junk drawer
  types.ts        # 900 lines
```

**Prefer** — Feature-first with layers inside. A feature is one folder, and its public surface is one file.

```bash
src/
  features/
    checkout/
      ui/           # screens + components
      model/        # hooks, state, domain logic
      api/          # queries, mutations, mappers
      index.ts      # the feature's public surface
  shared/
    ui/             # design system primitives
    lib/            # framework-agnostic helpers
  app/              # navigation, providers, bootstrap
```

### Keeping the domain layer pure

**Avoid** — A pricing rule that imports React Native can only be tested in a native environment, and cannot be reused anywhere else.

```ts
// features/checkout/model/pricing.ts
import { Platform } from "react-native";

export function totalWithFees(cents: number) {
  // Now this file needs a native runtime to test.
  const fee = Platform.OS === "ios" ? 30 : 25;
  return cents + fee;
}
```

**Prefer** — Platform detail is injected at the UI edge. The rule is a pure function you can test in milliseconds.

```ts
// features/checkout/model/pricing.ts — pure, trivially testable
export function totalWithFees(cents: number, feeCents: number) {
  return cents + feeCents;
}

// features/checkout/ui/CheckoutScreen.tsx — platform lives at the edge
const fee = Platform.select({ ios: 30, android: 25 }) ?? 25;
const total = totalWithFees(subtotal, fee);
```

### Enforcing the boundary

**Avoid** — Undefended conventions are suggestions. The first deadline erases them.

```json
// No rule. The convention lives in a wiki page
// that was last updated eleven months ago.
```

**Prefer** — The boundary now fails CI. That is the only enforcement that lasts.

```json
{
  "rules": {
    "no-restricted-imports": ["error", {
      "patterns": [{
        "group": ["**/features/*/!(index)*"],
        "message": "Import from the feature's index.ts, not its internals."
      }, {
        "group": ["react-native"],
        "importNames": ["Platform", "Dimensions"],
        "message": "Keep native APIs out of model/. Inject from the UI layer."
      }]
    }]
  }
}
```

## Review checklist

- [ ] Every feature is one folder with a single `index.ts` public surface.
- [ ] Nothing in `model/` imports from `react-native` or from another feature.
- [ ] Dependency direction is one-way: ui → model → api.
- [ ] Shared code earned its place in `shared/` by having two or more real consumers.
- [ ] An ESLint rule fails the build on deep feature imports.
- [ ] A new engineer can locate a screen's logic from the screen name alone.

---

From the John Felix Lim Frontend Engineering Playbook — https://github.com/JohnFelixLim