A recipe management app for Filipino mothers preserving family traditions
Ulamami combines three meaningful words:
- Ulam (Tagalog) - the main dish in Filipino cuisine
- Umami (Japanese) - the fifth basic taste, representing depth of flavor
- Mami (Filipino) - an affectionate term for "mommy"
- Features
- Tech Stack
- Development Process
- Tools & Libraries
- Setup Instructions
- Project Structure
- Futute Improvements
- Recipe Management: Full CRUD operations (Create, Read, Update, Delete)
- Smart Search: Real-time search with debouncing and request cancellation
- Favorites System: Optimistic UI updates with automatic rollback on failure
- Filtering: Filter recipes by category and difficulty level
- Persistent Storage: All data saved to localStorage with automatic initialization
- Responsive Design: Works seamlessly on desktop, tablet, and mobile
- Loading States: Clear visual feedback for all async operations
- Error Handling: User-friendly error messages with automatic retry
- Optimistic Updates: Instant UI feedback for all user actions
- Framework: React 18 with TypeScript
- Build Tool: Vite 5.x
- Styling: Tailwind CSS 3.x
- UI Components: shadcn/ui (Radix UI primitives)
- Icons: Lucide React
- API Simulation: Axios + axios-mock-adapter
- State Management: React Context API + Custom Hooks
- Storage: localStorage with TypeScript-safe wrappers
- Validation: Custom validator functions with detailed error messages
- Language: TypeScript (ES2020+)
- Package Manager: npm
- Code Quality: ESLint + TypeScript strict mode
- Initial Research: Studied async patterns and best practices
- Architecture Design: Planned layered architecture (UI β API β Helpers β Storage)
- Component Structure: Designed component hierarchy and data flow
- Color Palette: Created Filipino-inspired theme (Adobo red, Umami brown, Turmeric gold)
Foundation
- Set up Vite + React + TypeScript project
- Configured Tailwind CSS and shadcn/ui
- Created type definitions and interfaces
- Built mock API layer with axios-mock-adapter
Features
- Implemented CRUD operations with optimistic UI
- Built custom hooks (useRecipes, useFavorites, useSearch)
- Created reusable UI components
- Added validation layer with helpful error messages
Async Patterns
- Implemented request cancellation with AbortController
- Added retry logic with exponential backoff
- Built debounced search with race condition prevention
- Added optimistic updates with rollback functionality
Polish
- Refined UI/UX based on testing
- Added loading states and error handling
- Improved accessibility
- Documentation and code cleanup
Challenge 1: Race Conditions in Search
- Problem: When user types quickly, older search results could overwrite newer ones
- Solution: Implemented AbortController to cancel in-flight requests when new search starts
- Learning: Understanding cleanup functions in useEffect and proper request lifecycle management
Challenge 2: Optimistic UI Rollback
- Problem: Need to revert UI changes when API calls fail, but maintain correct state
- Solution: Used refs to store original state before optimistic update, then restore on error
- Learning: Refs are perfect for storing values that don't need to trigger re-renders
Challenge 3: TypeScript Error Handling
- Problem: TypeScript doesn't know the type of caught errors (always
unknown) - Solution: Created
getErrorMessageutility function with type guards - Learning: Proper TypeScript error handling patterns and type narrowing
Challenge 4: Preventing State Updates After Unmount
- Problem: setState calls after component unmounts causing React warnings
- Solution: Used
isMountedRefto track mount status and skip updates if unmounted - Learning: Component lifecycle management and proper cleanup in useEffect
Challenge 5: Duplicate Favorite Toggles
- Problem: Rapid clicking on favorite button sends multiple API requests
- Solution: Implemented version tracking to ignore stale responses and only apply latest
- Learning: Advanced concurrency patterns and request deduplication strategies
{
"react": "^18.3.1",
"react-dom": "^18.3.1",
"axios": "^1.7.9",
"axios-mock-adapter": "^2.1.0",
"lucide-react": "^0.468.0",
"clsx": "^2.1.1",
"tailwind-merge": "^2.6.0"
}- shadcn/ui: Copy-paste component library built on Radix UI
- Used components: Button, Card, Dialog, Input, Textarea, Badge, Toast, ScrollArea
- Why chosen: Accessible, customizable, no runtime dependency bloat
- Contribution: Customized theme colors to match Filipino-inspired palette
{
"typescript": "^5.6.2",
"vite": "^6.0.3",
"@vitejs/plugin-react": "^4.3.4",
"tailwindcss": "^3.4.17",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"@types/node": "^22.10.2"
}Claude (Anthropic)
- Used for:
- Architectural guidance and design pattern discussions
- Explaining async JavaScript concepts (AbortController, Promise handling, race conditions)
- Reviewing code structure and identifying potential issues
- Debugging TypeScript type errors
- Understanding best practices for React hooks and state management
- Discussing trade-offs between different implementation approaches
- How it helped:
- Provided conceptual explanations using different examples (game systems, library management) to help me understand patterns
- Guided me through thinking processes rather than just providing code
- Helped me understand WHY certain approaches are better, not just HOW to implement them
- Pointed out edge cases and potential bugs before they became issues
- Note: All code was written by hand to ensure learning and deep understanding
- React Documentation - Hooks, Context API, and component patterns
- TypeScript Documentation - Type system and advanced types
- MDN Web Docs - AbortController - Request cancellation
- Vite Documentation - Build configuration and optimization
- Tailwind CSS Documentation - Utility classes and customization
- shadcn/ui Documentation - Component usage and theming
- Building a Mock API in React - MockAPI Stucture
- shadcn/ui component source code - Learned accessibility patterns and component composition
- Axios documentation - Request interceptors and cancellation
- React Query source code - Studied optimistic update patterns (adapted approach without the library)
- Node.js: Version 18 or higher (LTS recommended)
- npm: Comes with Node.js
- Browser: Modern browser with ES2020+ support
-
Clone the repository
git clone <repository-url> cd ulamami
-
Install dependencies
npm install
-
Run the development server
npm run dev
-
Open in browser
Navigate to http://localhost:5173
npm run dev # Start development server with HMR
npm run build # Build for production
npm run preview # Preview production build locally
npm run lint # Run ESLint (if configured)Create a .env file in the root directory:
VITE_API_URL=http://localhost:5173/api
VITE_DB_KEY=ulamami_dbulamami/
βββ src/
β βββ components/ # React components
β β βββ ui/ # shadcn/ui components (auto-generated)
β β β βββ button.tsx
β β β βββ card.tsx
β β β βββ dialog.tsx
β β β βββ input.tsx
β β β βββ ...
β β βββ RecipeCard.tsx # Recipe display card
β β βββ RecipeDetailsDialog.tsx # Full recipe view modal
β β βββ RecipeFormDialog.tsx # Create/edit recipe form
β β βββ SearchBar.tsx # Search input with debouncing
β β
β βββ contexts/ # React Context providers
β β βββ RecipesContext.tsx # Global recipe state management
β β
β βββ hooks/ # Custom React hooks
β β βββ useRecipes.ts # Recipe CRUD with optimistic UI
β β βββ useFavorites.ts # Favorites with version tracking
β β βββ useSearch.ts # Debounced search (if separated)
β β
β βββ services/ # Data layer
β β βββ api/
β β β βββ axiosInstance.ts # Mock API with simulated latency/failures
β β βββ helpers.ts # Business logic functions
β β βββ storage.ts # localStorage wrapper utilities
β β βββ validators.ts # Validation functions (if separated)
β β
β βββ models/ # TypeScript interfaces
β β βββ IRecipe.ts # Recipe type definition
β β βββ IDatabase.ts # Database structure type
β β
β βββ data/ # Static data
β β βββ seedData.ts # Initial recipe data
β β
β βββ lib/ # Utilities and config
β β βββ utils.ts # Helper functions (retryWithBackoff, cn, etc.)
β β βββ env.ts # Environment configuration
β β βββ validators.ts # Validation logic (if centralized)
β β
β βββ App.tsx # Root component
β βββ main.tsx # App entry point
β βββ index.css # Global styles + Tailwind imports
β
βββ public/ # Static assets
βββ index.html # HTML template
βββ components.json # shadcn/ui configuration
βββ tailwind.config.js # Tailwind configuration
βββ tsconfig.json # TypeScript configuration
βββ vite.config.ts # Vite configuration
βββ package.json # Dependencies and scripts
βββ README.md # This file
If I had more time, I would add:
- User Authentication: Multi-user support with Firebase/Supabase
- Image Upload: Direct image upload instead of URLs
- Recipe Ratings: Star ratings and reviews
- Cooking Timer: Built-in timer for recipe steps
- Meal Planning: Weekly meal planner calendar
- Shopping List: Auto-generate from recipe ingredients
- Recipe Sharing: Share recipes via link or social media
- Print View: Printer-friendly recipe format
- Nutrition Info: Automatic nutrition calculation
- Unit Conversion: Convert measurements (cups β grams)
- Offline Support: Service worker for offline access
- Dark Mode: Full dark theme support
- Recipe Import: Import from URLs (using web scraping)
Your Name
- GitHub: @candicejoyballarta
- LinkedIn: Candice Ballarta
- Email: candiceballarta@gmail.com
This project was created as a take-home assignment and is available for educational purposes.
- Inspiration: Filipino home cooking and the mothers who preserve family recipes
- UI Design: shadcn/ui component library and Tailwind CSS
- Cultural Heritage: The rich culinary traditions of Filipino cuisine
- Lines of Code: ~3,500 (excluding node_modules)
- Components: 15+ React components
- Custom Hooks: 3 specialized hooks
- Type Definitions: 100% TypeScript coverage
- Async Patterns: 7 advanced patterns implemented
- Test Coverage: Manual testing of all async edge cases
Built with β€οΈ for Filipino home cooks