# Fractal Components in React

Canonical: https://gaboesquivel.com/blog/2024-11-react-fractal-compoundnents

**Published:** 2024-11-25
**Summary:** How I organize React applications into self-similar modules and compound components to keep features manageable as they grow.
**Categories:** Engineering, Frontend
**Technologies:** ReactJS, TypeScript, NextJS

I structure [React](https://gaboesquivel.com/tech/reactjs) apps so the same shape repeats at every scale, and so small pieces get more valuable the more they are reused. Fractals and compounding are the two metaphors I use for that, and applied to a codebase they mean a consistent, layered design where extensions don't pile on debt.

## Fractals: self-similarity

A fractal keeps the same shape at every scale, the way a fern leaflet looks like the whole frond. I want that in a codebase too, where a route folder looks like a feature folder and a component folder looks like both.

## Compounding in finance and code

In finance, compounding means earning returns on reinvested returns, so small, regular contributions grow into large balances over time. In software, the equivalent is a system where each small improvement or extension adds value without adding debt. Fractal components are reusable building blocks that share one shape, so you can extend the system without inventing a new pattern each time.

## React fractal components

In React, fractal design combines self-similarity with modularity through four practices:

1. **Uniform folder structures:** every folder, whether it holds a route, a feature, or a component, mirrors the same layout.

2. **One stack everywhere:** every component follows the same standards:
   - **tanstack/react-query** for data fetching and caching with a predictable query model.
   - **viem** for low-level [Ethereum](https://gaboesquivel.com/tech/ethereum) interactions with a modern [TypeScript](https://gaboesquivel.com/tech/typescript) API.
   - **wagmi** for React hooks over Ethereum, built on react-query and viem so chain reads follow the same data-fetching pattern.
   - **nuqs** for URL-based state, which decouples components from each other's internal logic.
   - **Supabase** as the [backend](https://gaboesquivel.com/backend), with API logic in dedicated folders so backend and frontend code stay portable.

3. **A shared design system:** shadcn/ui and [Tailwind CSS](https://gaboesquivel.com/tech/tailwind) keep the UI consistent and portable, including for generative UI workflows.

4. **Collocation with AHA:** components are extracted only when reuse becomes obvious, following Avoid Hasty Abstractions.

![Self-similar component hierarchies at large and small scales](https://gaboesquivel.com/images/diagrams/2024-11-react-fractal-compoundnents.svg)

Self-similar component organization lets feature modules reuse the same composition principles as smaller UI structures.

## Folder structure

Each package repeats the same hierarchy, so a developer who knows one folder can predict the next:

```bash
repo/
  - supabase/
    - src/
        - market/
           - index.ts
           - types.ts
        - account/
          - index.ts
          - types.ts
  - hooks/
    - src/
      - market/
        - index.ts
        - types.ts
        - use-markets.ts
      - account/
        - index.ts
        - types.ts
        - use-account.ts
  - app/
    - src/
      - market/
        - list/
          - index.tsx
          - types.ts
        - detail/
          - use-market-collateral.ts
          - index.tsx
          - types.ts
      - account/
        - index.tsx
        - hooks/
          - use-account-health.ts
          - use-account-positions.ts
          - types.ts
        - health.tsx
        - positions.tsx
        - types.ts    
```

## Data fetching with React Query

Data fetching follows one pattern: the query function lives in the Supabase package and takes its client as an argument, so the same function runs from a hook on the frontend or from server code, and the hook only adds caching:

```ts
// repo/hooks/useMarkets.ts

import { useQuery } from '@tanstack/react-query'
import { createSupabaseClient } from '@repo/db'
import { getMarkets } from '@/api/market'

export function useMarkets() {
  return useQuery({
    queryKey: ['markets'],
    queryFn: async () => {
      const supabase = await createSupabaseClient()
      return getMarkets({ supabase })
    }
  })
}
```