# MVVM Pattern

Technical description of the MVVM (Model-View-ViewModel) pattern used in Ledger Wallet to separate UI components from business logic.

---

## Pattern Structure

```
ComponentName/
├─ index.tsx                    # Exports the connected component
└─ useComponentNameViewModel.ts # Contains business logic
```

When a component connects to outer layers (network calls, store data), create a dedicated hook named `use{ComponentName}ViewModel`. The `ViewModel` suffix identifies hooks dedicated to a specific component that are not intended for reuse elsewhere.

---

## Implementation

### Correct Way

> **Note:** Inject the ViewModel hook result as props:

```tsx
// index.tsx
import { useMarketListViewModel } from "./useMarketListViewModel";

function View(props) {
  // Pure UI component - only renders based on props
  return (
    <div>
      <h1>{props.title}</h1>
      <ul>
        {props.items.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <button onClick={props.onRefresh}>Refresh</button>
    </div>
  );
}

// Connect View to ViewModel
const MarketList = () => <View {...useMarketListViewModel()} />;

export default MarketList;
```

```tsx
// useMarketListViewModel.ts
import { useSelector, useDispatch } from "react-redux";
import { useCallback } from "react";
import { selectMarketItems, fetchMarketData } from "../data-layer";

export function useMarketListViewModel() {
  const dispatch = useDispatch();
  const items = useSelector(selectMarketItems);
  const isLoading = useSelector(selectIsLoading);

  const onRefresh = useCallback(() => {
    dispatch(fetchMarketData());
  }, [dispatch]);

  return {
    title: "Market",
    items,
    isLoading,
    onRefresh,
  };
}
```

### Wrong Way

> **Caution:** Don't mix ViewModel logic directly in the component:

```tsx
// ❌ Wrong - mixing concerns
function MarketList() {
  const props = useMarketListViewModel();

  // Business logic mixed with UI
  if (props.isLoading) {
    return <Spinner />;
  }

  return <div>{props.data}</div>;
}

export default MarketList;
```

> **Warning:** **Why?** Separating the ViewModel call from the View makes testing easier. You can test the View with different props without mocking hooks, and test the ViewModel logic independently.

---

## When to Use

- Components that connect to Redux store
- Components that make API calls
- Components with complex business logic
- Screens and major feature components

## When NOT to Use

- Simple presentational components
- Components that only receive props from parents
- Pure utility components (buttons, inputs, etc.)

---

## See also

- [Component Patterns](./component-patterns) — Rule 9 covers the `useViewModel` injection pattern
- [How to Structure a Feature](../how-to/structure-a-feature) — How to set up a feature module with MVVM
- [Architecture Decisions](../explanation/architecture-decisions) — Why we use MVVM and its benefits
