Day 5: One Feature, Three Bugs, and the Reality of Senior Engineering
After the major performance optimization on Day 4, the app was lean and ready for its next core feature. The plan for Day 5 was to build the user “Watchlist” from scratch. On paper, it was a 40-minute task.
In reality, the session clocked in at 2 hours and 55 minutes.
The good news is that I successfully built the entire feature. The real story, however, is that making a new feature work is only half the battle. Making it work correctly and robustly is where the real engineering begins. This is the unfiltered reality of senior-level development: a simple feature can uncover a web of complex challenges that must be solved.
Step 1: Building the Initial Watchlist Feature
The goal was to allow users to save their favorite coins for easy tracking. The initial implementation plan was straightforward:
- Use
@react-native-async-storage/async-storage
for local persistence. - Create a custom
useWatchlist
hook to encapsulate the logic for adding, removing, and loading favorited coins. - Add a “star” icon to the
DetailsScreen
header to toggle a coin’s status. - Build the
WatchlistScreen
to display only the favorited coins.
With the plan set, I started building. And almost immediately, after getting the initial code in place, the plan met reality.
The First Hurdle: The “Stale State” Bug
After implementing the hook and the UI, I tested the flow. I could star a coin on the Details screen, but when I navigated to the Watchlist screen, it was still empty. The change only appeared after a full app restart.
Here is the buggy behavior in action:
The Bug: The Watchlist screen doesn't update until the app is restarted.
The Cause: This was a classic state management problem. My custom hook created a separate, isolated state for each screen. The DetailsScreen
and the WatchlistScreen
had different instances of the state and were completely unaware of each other.
The Fix: The solution was a necessary refactor. I moved all the watchlist logic into our global Zustand store. By creating a single source of truth, any change made on one screen would now be instantly and reactively available everywhere else.
Here is the corrected version after the fix:
The Fix: With a global store, the Watchlist screen updates instantly.
The Second Hurdle: The Infinite Render Loop Crash
With the state management refactored, a much more sinister bug appeared. The app would crash with a terrifying “Maximum update depth exceeded” error whenever a price updated while I was on the DetailsScreen.
The Cause: This is a classic React infinite loop. A WebSocket price update caused the DetailsScreen
to re-render. My useLayoutEffect
hook, which set the header’s star icon, depended on the entire coin
object. Since the object reference changed on every re-render, the effect would run again, triggering another re-render, and so on, until the app crashed.
The Fix: An architectural one. I refactored the DetailsScreen
to use granular selectors with Zustand (subscribing to individual data points instead of the whole object) and wrapped the star icon’s onPress
handler in a React.useCallback
hook to give it a stable reference. This permanently broke the loop.
The Third Hurdle: The API Rate Limit Crash
With the app stable, I noticed it would still sometimes crash with an AxiosError: Request failed with status code 429
. I was hitting the free CoinGecko API’s rate limit by fetching chart data every single time the DetailsScreen was opened.
The Fix: I implemented a simple but effective caching strategy in my Zustand store. Now, my fetchChartData
action first checks if data for a coin exists in a chartDataCache
object. If it does, it returns the cached data instantly. If not, it fetches from the API and saves it to the cache for next time.
The Final Challenge: A Pragmatic Decision
After fixing all the crashes, one warning remained: the VirtualizedList: You have a large list that is slow to update...
log. Here, I made a conscious, senior-level decision: I will not fix this for now. For a 7-day challenge, a major refactor to a library like FlashList is out of scope. Acknowledging a known limitation and making a pragmatic decision based on project constraints is a critical engineering skill.
Conclusion: More Than Just a Feature
Today, we didn’t just build the Watchlist feature; we hardened the entire application. We took a simple feature, found its flaws, and re-architected it to be robust, performant, and crash-free. These are the challenges that truly level up your skills.
With the app now robust and crash-free, the focus for the next session shifted from pure functionality to form and feel. The next challenge was to take an app that works and make it an app that feels delightful to use.
Next up on Day 6: The Art of Polish—Refining UI and Bringing it to Life
Join the Discussion
1. What part of this post was most valuable to you?
- (A) The state management (Zustand) solution.
- (B) The breakdown of the infinite render loop bug.
- (C) The pragmatic decision not to fix the final warning.
Let me know your answer (A, B, or C) in the comments below!
2. A Question For You: What’s the most complex bug you’ve ever had to hunt down? What was the “aha!” moment that finally led to the solution?
The best way to follow this 7-day sprint is by joining my newsletter for daily updates directly in your inbox.