Skip to main content

Project Structure

CryptoTracker follows a modular architecture built with Expo and React Native, organizing code by feature and responsibility:
cryptotracker/
├── app/                      # Expo Router file-based routing
│   ├── (tabs)/              # Tab navigation group
│   │   ├── _layout.tsx      # Tab bar configuration
│   │   └── index.tsx        # Home screen route
│   ├── crypto/              # Crypto detail routes
│   │   └── [id].tsx         # Dynamic route for crypto details
│   ├── _layout.tsx          # Root layout with theme
│   └── +not-found.tsx       # 404 error page
├── src/
│   ├── components/          # Reusable UI components
│   │   ├── CryptoCard.tsx   # Individual crypto display card
│   │   ├── HomeHeader.tsx   # Header with search/filters
│   │   └── SearchBar.tsx    # Search input component
│   ├── context/             # React Context providers
│   │   └── FilterContext.tsx # Global filter state
│   ├── hooks/               # Custom React hooks
│   │   └── useCryptoData.ts # API data fetching hook
│   ├── models/              # TypeScript interfaces & classes
│   │   └── Crypto.ts        # Crypto data models
│   ├── screens/             # Full-page screen components
│   │   ├── HomeScreen.tsx   # Main listing screen
│   │   └── CryptoDetailScreen.tsx
│   ├── services/            # API & external services
│   │   └── cryptoService.ts # CoinLore API integration
│   ├── utils/               # Utility functions
│   │   └── formatters.ts    # Price/number formatting
│   └── constants/           # App-wide constants
│       └── Colors.ts        # Theme color definitions
└── assets/                  # Static assets (images, fonts)
The app/ directory defines your routes using Expo Router’s file-based routing, while src/ contains all business logic, components, and utilities.

Data Flow Architecture

The application follows a unidirectional data flow pattern:

Data Flow Steps

1

API Request

The useCryptoData hook initiates a fetch request to the CoinLore API when the component mounts:
src/hooks/useCryptoData.ts:30-34
const fetchData = async () => {
  const res = await fetch('https://api.coinlore.net/api/tickers/');
  const json = await res.json();
  // Process data...
};
2

Data Transformation

Raw API responses are transformed into typed Crypto class instances:
src/hooks/useCryptoData.ts:36-40
const cryptos = json.data.map(
  (item: any) =>
    new Crypto(item.id, item.name, item.rank, 
               item.symbol, item.price_usd, item.percent_change_24h)
);
3

State Management

Data is stored in component state and made available to the UI:
src/hooks/useCryptoData.ts:19-25
const [data, setData] = useState<CryptoApiResponse[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
4

Filtering

The HomeScreen combines API data with global filter state:
src/screens/HomeScreen.tsx:31-39
const filtered = data.filter((crypto) => {
  const nameMatch = crypto.name.toLowerCase().includes(filter.text.toLowerCase());
  const symbolMatch = crypto.symbol.toLowerCase().includes(filter.text.toLowerCase());
  const minOk = filter.minPrice === null || Number(crypto.price_usd) >= filter.minPrice;
  const maxOk = filter.maxPrice === null || Number(crypto.price_usd) <= filter.maxPrice;
  return (nameMatch || symbolMatch) && minOk && maxOk;
});
5

Rendering

Filtered data is mapped to CryptoCard components for display:
src/screens/HomeScreen.tsx:74-76
{filtered.map((crypto) => (
  <CryptoCard key={crypto.id} crypto={crypto} />
))}

Component Hierarchy

The component tree demonstrates how data and state flow through the application:
The root layout provides theme configuration and font loading:
app/_layout.tsx:65-76
function RootLayoutNav() {
  const colorScheme = useColorScheme();
  return (
    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <Stack>
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
      </Stack>
    </ThemeProvider>
  );
}
The tab layout wraps the application in the FilterProvider context:
app/(tabs)/_layout.tsx:38-54
<FilterProvider>
  <Tabs
    screenOptions={{
      tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
    }}
  >
    <Tabs.Screen
      name="index"
      options={{
        title: 'Inicio',
        tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
        header: () => <HomeHeader />,
      }}
    />
  </Tabs>
</FilterProvider>
The home screen consumes both the API data hook and filter context:
src/screens/HomeScreen.tsx:17-22
const HomeScreen = () => {
  const { data, loading, error } = useCryptoData();
  const { filter } = useFilter();
  // ... filtering and rendering logic
};
Individual cards receive crypto data as props and handle navigation:
src/components/CryptoCard.tsx:21-40
const CryptoCard = ({ crypto }: Props) => {
  const router = useRouter();
  return (
    <TouchableOpacity
      onPress={() =>
        router.push({
          pathname: '/crypto/[id]',
          params: { id: crypto.id },
        })
      }
    >
      {/* Card UI */}
    </TouchableOpacity>
  );
};

Type Safety with TypeScript

The application leverages TypeScript for type safety throughout the data flow:
src/models/Crypto.ts:9-26
export interface CryptoApiResponse {
  id: string;
  symbol: string;
  name: string;
  rank: number;
  price_usd: string;
  percent_change_24h: string;
  percent_change_1h: string;
  percent_change_7d: string;
  market_cap_usd: string;
  volume24: number;
  // ... additional fields
}

Service Layer

The service layer abstracts API communication logic:
src/services/cryptoService.ts:8-21
export const getCryptoList = async (
  start: number = 0,
  limit: number = 100
): Promise<CryptoApiResponse[]> => {
  const url = `https://api.coinlore.net/api/tickers/?start=${start}&limit=${limit}`;
  
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Error al obtener lista de criptos: ${response.status}`);
  }
  
  const json = await response.json();
  return json.data;
};
The cryptoService provides paginated data fetching and detail lookup functionality, supporting future features like infinite scroll or search optimization.

Key Architectural Patterns

Separation of Concerns

  • Routing: Handled by Expo Router in app/ directory
  • UI Components: Reusable components in src/components/
  • Business Logic: Custom hooks in src/hooks/
  • Data Models: TypeScript classes/interfaces in src/models/
  • API Integration: Service functions in src/services/

Container/Presenter Pattern

  • Screens (HomeScreen, CryptoDetailScreen) act as containers that fetch data and manage state
  • Components (CryptoCard, SearchBar) are presentational, receiving data via props

Custom Hooks for Reusability

The useCryptoData hook encapsulates data fetching logic, making it reusable across multiple screens:
const { data, loading, error } = useCryptoData();
This pattern allows any component to access crypto data without duplicating fetch logic.

Next Steps

Navigation

Learn how Expo Router powers file-based routing

State Management

Explore Context API and custom hooks

Build docs developers (and LLMs) love