Skip to Content
We're improving our docs. Share your experience and help shape what comes next.

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

Inject the ViewModel hook result as props:

// 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;
// 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

Don’t mix ViewModel logic directly in the component:

// ❌ 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;

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

Last updated on
Ledger
Copyright © Ledger SAS. All rights reserved. Ledger, Ledger Stax, Ledger Flex, Ledger Nano, Ledger Nano S, Ledger OS, Ledger Wallet, [LEDGER] (logo), [L] (logo) are trademarks owned by Ledger SAS.