ReactJS is cool but it become very quickly a massive pile of stinky mud if you are not careful. Here are few tips you can use to keep things decent.
Maintanable React Js Tips
Not everything needs to be reusable — and that’s a good thing.
Core components like buttons and cards are meant to be global and reusable because they have a fixed design and limited feature set.
But if you need a variation, like a `ProfileCard` with extra logic, keep it local to the relevant feature folder.
Only promote a component to the global/shared layer when it’s used in multiple features.
This naturally leads to having modular, self-contained “mini-projects” (aka features) that are as independent as possible.
if you have to use TypeScript, then use it strictly
I don’t like TS too much, but if it is there, then try to use it in the strictest way possible.
Make sure TypeScript complains when something breaks.
- Use precise types. Avoid loose `string` or `any` types when you can define a clear union (`“x” | “y”`) or an enum.
- Avoid switch statements with default cases; use `Record<Enum, Value>` for mapping to ensure coverage.
- When integrating with external sources (like APIs), auto-generate types whenever possible and infer instead of retyping.
- For example, prefer `Some<ApiType, “name”>` over `{ name: string }`.
This ensures all type changes are caught early and reduces bugs from drifting external dependencies.
Avoid useEffect unless absolutely necessary.
If you’re not syncing with the outside world (e.g., subscriptions, timers, API calls), you probably don’t need it.
Almost all the cases using `useEffect` could be done in a more explicit way.
- Instead of reacting to data change, prefer using an `onChange` callback to handle it directly.
- Cases where `useEffect` was used on mount often revealed deeper architectural or logic issues.
`useEffect` is essentially an escape hatch out of React and should only be used for that purpose.
Even the React docs themselves emphasize this point.
Most logic can be modeled with event-driven thinking or derived/computed values.
If it’s a “computed” value, it shouldn’t be in state.
|
|
Separate data loading from UI rendering.
When a component depends on loaded data, break it into two layers:
- The outer component handles loading (API calls, route params, etc.) and displays fallbacks like spinners.
- The inner component assumes it has everything it needs — no `null` or `undefined`.
If the loading logic involves different paths (e.g., branching between multiple endpoints), split it further and only combine everything when rendering the final form/component.
Favor small components and explicit interfaces.
Smaller components are easier to understand, test, and reuse.
As you split things up, clear interfaces emerge naturally, and you’ll often spot patterns that can become feature-local reusable components or hooks.
Keep these local — don’t mix them with global/shared components unless they’re truly cross-feature.
Keep the number of dependancies as low as you can
You have a component library? Great - stick to it - but everything else, unless absolutly necessary and core to the app, write yourself. Do not use some fancy extra library for 2 components, it will come back to bite you later.
Don’t pick redux to manage state
That’s an opinion - and that’s mine but not only mine - and for a good reason - seriously today there are better options (as in simpler to keep your app maintanable over time) for managing state. And yes I know Redux has improved a lot, and if you like it, then fine, I am not your mother, you do you. But I told you, so now you know.
Here are some alternative to Redux in for your React applications:
- Zustand
- A minimalistic and intuitive state management library using hooks with no boilerplate.
- Recoil
- Atom-based global state management with a React-friendly API, good for fine-grained control.
- Jotai
- A simpler atomic state library inspired by Recoil, with a minimal and flexible core.
- React Query (TanStack Query)
- Best suited for managing server state — handles fetching, caching, and background updates.
- SWR
- Developed by Vercel, optimized for remote data fetching with built-in caching and revalidation.
- MobX
- A reactive state management solution using observables, ideal for complex UI logic with minimal code.