A production-quality React Native mobile application built with Expo and TypeScript. Browse, search, and explore employee profiles with a clean, modern UI that adapts to light and dark mode.
| Employee List | Search | Employee Detail |
|---|---|---|
| Skeleton loading → FlatList with cards | Real-time client-side filtering | Animated profile with sections |
| Category | Technology |
|---|---|
| Framework | Expo (SDK 54) + React Native 0.81 |
| Language | TypeScript (strict mode) |
| Navigation | Expo Router v6 (file-based) |
| Data Fetching | @tanstack/react-query v5 |
| HTTP Client | Axios v1 |
| Offline Cache | React Query Persist + AsyncStorage |
| Testing | Jest v29 + React Native Testing Library |
| Animations | React Native Animated API |
| Haptic Feedback | expo-haptics |
| Icons | @expo/vector-icons (Ionicons) |
| Dark Mode | System-based via useColorScheme |
- Employee List — FlatList with pull-to-refresh, skeleton loading, and error retry
- Search — Real-time client-side filtering by first name, last name, or full name
- Employee Details — Profile photo, contact info, work details, and personal section
- Navigation — Expo Router file-based routing with smooth animated transitions
- Offline Caching — React Query data persisted to AsyncStorage (24-hour TTL)
- Dark Mode — Automatic system-based theme with full color token support
- Animations — Staggered slide-up list items, spring detail entrance, page transitions
- Haptic Feedback — Light tactile response on card press
- Tappable Contacts — Email and phone open native
mailto:/tel:handlers - Image Fallback — Initials avatar when profile image fails to load
- Skeleton Loaders — Shimmer loading states for both list and detail screens
- Error Boundary — App-level crash protection with recovery UI
- Unit Tests — 86 tests across components, hooks, and utility functions
employee-directory/
├── app/ # Expo Router screens (file-based routing)
│ ├── _layout.tsx # Root layout — QueryClientProvider + Stack navigator
│ ├── index.tsx # Home screen — Employee list with search
│ └── employee/
│ └── [id].tsx # Dynamic route — Employee detail screen
│
├── src/
│ ├── api/
│ │ ├── client.ts # Axios instance with interceptors
│ │ ├── employees.ts # Employee API methods (getAll, getById)
│ │ └── index.ts
│ │
│ ├── components/
│ │ ├── EmployeeCard.tsx # List card with slide-up animation + haptics
│ │ ├── SearchBar.tsx # Search input with Ionicons + clear button
│ │ ├── LoadingState.tsx # Animated skeleton loader (list)
│ │ ├── DetailSkeleton.tsx # Animated skeleton loader (detail screen)
│ │ ├── ErrorState.tsx # Error display with retry button
│ │ ├── ErrorBoundary.tsx # App-level error boundary
│ │ ├── EmptyState.tsx # Empty search results display
│ │ ├── DetailRow.tsx # Reusable detail row (supports onPress)
│ │ └── index.ts
│ │
│ ├── hooks/
│ │ ├── queryKeys.ts # Centralized React Query key factory
│ │ ├── useEmployees.ts # Fetch all employees
│ │ ├── useEmployee.ts # Fetch single employee by ID
│ │ └── index.ts
│ │
│ ├── store/
│ │ └── queryClient.ts # QueryClient + AsyncStorage persister
│ │
│ ├── theme/
│ │ ├── colors.ts # Light & dark color tokens
│ │ ├── typography.ts # Font sizes, weights, and text styles
│ │ ├── spacing.ts # Spacing scale and border radii
│ │ └── index.ts # useTheme() hook
│ │
│ ├── types/
│ │ ├── employee.ts # Full Employee interface (matches DummyJSON API)
│ │ └── index.ts
│ │
│ └── utils/
│ ├── formatters.ts # getFullName, getInitials, filterEmployeesByName
│ └── index.ts
│
├── __tests__/
│ ├── components/
│ │ ├── EmployeeCard.test.tsx
│ │ ├── SearchBar.test.tsx
│ │ └── Pagination.test.tsx
│ ├── hooks/
│ │ ├── useEmployees.test.ts
│ │ └── usePagination.test.ts
│ └── utils/
│ └── formatters.test.ts
│
├── assets/ # Icons and splash screen
├── app.json # Expo configuration
├── babel.config.js # Babel + module-resolver for path aliases
├── jest.setup.js # Jest global mocks (expo-router, AsyncStorage)
├── tsconfig.json # TypeScript config with @/ path alias
└── package.json
Routes are derived from the file system under /app. This gives typed routes, deep linking support, and removes boilerplate navigation code. The _layout.tsx at the root wraps the entire app with QueryClientProvider.
- 5-minute stale time — data is fresh for 5 minutes before a background refetch
- 30-minute GC time — unused queries stay in cache for 30 minutes
- AsyncStorage persistence — cached data survives app restarts (24-hour TTL)
- Query key factory — centralized
queryKeysobject prevents key collisions
A single apiClient instance with base URL, timeout, and response interceptors. All API calls go through employeesApi which returns typed responses. Error messages from the server are normalized into Error objects.
The useTheme() hook reads useColorScheme() and returns the appropriate color set (light/dark), plus typography, spacing, and border radius tokens. Components call makeStyles(colors) inside the component body so styles react to theme changes without re-renders.
@/ maps to ./src/ via both tsconfig.json (for type checking) and babel-plugin-module-resolver (for runtime). Tests use moduleNameMapper in Jest config.
removeClippedSubviewson AndroidinitialNumToRender={20},maxToRenderPerBatch={20},windowSize={5}getItemLayoutfor fixed card heights (eliminates layout measurement overhead)useCallbackforrenderItemandkeyExtractor
Base URL: https://dummyjson.com
| Endpoint | Description |
|---|---|
GET /users?limit=100 |
Fetch up to 100 employees |
GET /users/:id |
Fetch single employee by ID |
- Node.js 18+
- npm or yarn
- Expo Go app on your iOS/Android device (or a simulator)
# 1. Clone or navigate to the project
cd employee-directory
# 2. Install dependencies
npm install
# 3. Start the development server
npx expo startThen:
- Press
ito open in iOS Simulator - Press
ato open in Android Emulator - Scan the QR code with Expo Go on your physical device
# Run all tests once
npm test
# Watch mode
npm run test:watch
# Generate coverage report
npm run test:coverage| Suite | Tests |
|---|---|
formatters.test.ts |
10 tests — string formatting + filtering |
EmployeeCard.test.tsx |
5 tests — render, press, accessibility |
SearchBar.test.tsx |
6 tests — input, clear button, callbacks |
Pagination.test.tsx |
56 tests — page navigation, window, accessibility |
useEmployees.test.ts |
3 tests — loading, success, error states |
usePagination.test.ts |
6 tests — pagination logic, edge cases |
| Total | 86 tests |
npm run type-checkThe app uses https://dummyjson.com as the API. No environment variables are required for development.
To change the API base URL, edit src/api/client.ts:
const BASE_URL = 'https://your-api.com';This project is structured to scale:
- Add new screens — drop a file in
/app/ - Add new API endpoints — add a method to
src/api/employees.ts - Add new hooks — create in
src/hooks/, export fromsrc/hooks/index.ts - Add new components — create in
src/components/, export from index - Extend the theme — add tokens to
src/theme/colors.ts
MIT