Most React performance problems I inherit trace back to one hook used as a junk drawer. Data fetching, subscriptions, DOM measurement and derived state all pile into a single effect, and the component re-renders itself into a swamp. The fix is rarely memoization — it's separation.
One effect, one job
Split effects by lifecycle. The subscription effect subscribes and cleans up; the fetching effect depends only on the query; the analytics effect fires on route change. Each gets the smallest correct dependency array, and each can be reasoned about alone.
When a dependency array keeps growing, that's the code telling you the logic belongs elsewhere — usually in an event handler or a reducer, where it runs once per user action instead of once per render.
Cleanup is not optional
Every effect that reaches outside React — timers, sockets, observers, in-flight requests — needs a cleanup function. In development, StrictMode mounts effects twice precisely to expose the ones you forgot. Treat double-invocation warnings as bugs, not noise.
- Abort in-flight fetches when the query or component unmounts.
- Disconnect observers and remove listeners in the return function.
- Move derived values out of effects into render or useMemo.
Apply those three habits and most "React is slow" complaints evaporate — leaving you free to spend performance budget where users actually feel it.
Mateo Alvarado
Web developer & UI/UX designer, Semicolon Studio