
After 40+ projects and three years in production environments, here are the React lessons I wish I had learned on day one — not year three.
I started writing React when I was still figuring out what a closure was. I built forms that reset themselves, state that mutated in loops, and components that rendered fourteen times per keystroke. I shipped all of it. The clients were happy. The code was not.
Three years and 40+ projects later, I have a very different relationship with the framework. Not reverence — React is a tool, not a religion — but an honest understanding of where it rewards you and where it quietly punishes you if you aren't paying attention.
Everyone talks about single responsibility. Fewer people talk about what happens when you violate it at scale. I once maintained a component that accepted 23 props. It rendered a button. The component knew about authentication state, theme, analytics events, modal state, and three different loading states. Touching it was a negotiation with the past.
The fix isn't just 'make smaller components.' It's thinking about what a component owns versus what it merely displays. Owned state lives inside. Display-only data flows in via props. Side effects go in hooks. When you draw that line clearly, the component stops fighting you.
I resisted TypeScript for almost a year. It felt like ceremony — annotating things I already 'knew' were strings. Then a client's API silently changed a field from a number to a string. In plain JavaScript, you don't find out until the bug report. With TypeScript, the build fails and you fix it before it ships.
TypeScript doesn't slow you down. It transfers the pain from production to your editor, where it costs nothing.
The places where TypeScript feels annoying are almost always the places where your architecture is unclear. Use the friction as a signal, not a reason to add `as any`.
I've used Redux, MobX, Zustand, Jotai, React Query, and combinations of all of them. My current default: `useState` + `useReducer` for local state, React Query for server state, and Zustand for the rare global UI state that actually needs to be global. That's it.
The mistake I made early was treating global state as the solution to prop drilling. The real solution to prop drilling is component composition — restructuring the tree so data doesn't have to travel through five layers to reach one component that needs it.
Reaching for `React.memo` and `useCallback` everywhere is a sign that something is wrong with your component tree, not a sign of good practice. If a component re-renders too often, the question isn't 'how do I memo this?' — it's 'why does the parent hold this state?'
The cleanest code I ever wrote was for a project that never launched. I refactored it three times, perfected the architecture, and then the client ran out of budget. Meanwhile, a project I shipped in two weeks with imperfect code has been running in production for two years and has made real money for a real person.
Code quality matters. But quality is defined by what the code enables, not what it looks like. A well-architected app that ships is worth infinitely more than a perfect one that doesn't.
Then you’re in the right place. Get the best solution you’re looking for. Just reach out and let me know!