ReactJS Principles for Optimized Code
โ๏ธ ReactJS Principles for Optimized Code
๐ Write Less. Render Smarter. Ship Faster.
React makes it incredibly easy to build interfacesโbut writing React code that works is not the same as writing React code that scales.
As applications grow, small decisions around component structure, state, effects, rendering, data fetching, and JavaScript can turn into major performance problems.
This guide covers the principles, patterns, functions, optimization techniques, hacks, and common mistakes that help you write clean, predictable, maintainable, and high-performance React applications.
๐ง 1. The Golden Principle: Optimize the Architecture First
Before thinking about useMemo(), useCallback(), or lazy loading, ask:
โWhy is this component rendering in the first place?โ
A well-designed React application naturally requires fewer optimizations.
โ Poor architecture
function App() {
const [search, setSearch] = useState("");
const [theme, setTheme] = useState("dark");
const [user, setUser] = useState(null);
const [cart, setCart] = useState([]);
return (
<Dashboard
search={search}
theme={theme}
user={user}
cart={cart}
/>
);
}
One large component owns unrelated state.
โ Better architecture
function App() {
return (
<>
<Header />
<Search />
<Dashboard />
<Cart />
</>
);
}
Each component owns the state it actually needs.
๐ฏ Principle
Keep state as close as possible to where it is consumed.
This is called state colocation.
โก 2. Understand React Rendering
A React component can render when:
- Its state changes
- Its parent renders
- A context value changes
- Its external store changes
- Its subscribed data changes
Rendering does not automatically mean DOM manipulation.
React roughly follows:
State Update
โ
Component Render
โ
Virtual DOM
โ
Reconciliation
โ
DOM Commit
โ
Browser Paint
Understanding this pipeline is essential for optimization.
๐งฉ 3. Components Should Have One Responsibility
Avoid giant components.
โ Bad
function Dashboard() {
// API calls
// authentication
// filtering
// charts
// forms
// tables
// modal
// notifications
// business logic
}
โ Better
Dashboard
โโโ Header
โโโ Statistics
โโโ SalesChart
โโโ OrdersTable
โโโ OrderModal
โโโ Notifications
Each component should have a clear responsibility.
Benefits
- Easier testing
- Easier debugging
- Smaller re-render boundaries
- Better reuse
- Easier maintenance
๐ง 4. Keep State Minimal
One of the biggest React optimization principles:
Donโt store something in state if you can calculate it.
โ Bad
const [firstName, setFirstName] = useState("Lakhveer");
const [lastName, setLastName] = useState("Rajput");
const [fullName, setFullName] = useState("Lakhveer Rajput");
Now you must synchronize three pieces of state.
โ Better
const [firstName, setFirstName] = useState("Lakhveer");
const [lastName, setLastName] = useState("Rajput");
const fullName = `${firstName} ${lastName}`;
One source of truth.
๐ฅ 5. Donโt Abuse useEffect()
useEffect() is one of the most misunderstood React APIs.
Use effects for synchronizing React with external systems.
Examples:
- API subscriptions
- Browser APIs
- WebSocket connections
- Timers
- External libraries
- DOM integrations
โ Donโt do this
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(price * quantity);
}, [price, quantity]);
Youโre creating an unnecessary render.
โ Do this
const total = price * quantity;
Rule
If something can be calculated during rendering, donโt use an effect to calculate it.
โก 6. useMemo() โ Cache Expensive Calculations
useMemo() remembers a calculated value.
const filteredUsers = useMemo(() => {
return users.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase())
);
}, [users, search]);
The calculation runs again only when:
users
OR
search
changes.
โ ๏ธ Important
Donโt use:
const value = useMemo(() => a + b, [a, b]);
for trivial calculations.
Memoization itself has a cost.
Use useMemo() when:
- Calculation is expensive
- Large arrays are processed
- Sorting/filtering is expensive
- Referential equality matters
- Profiling shows a performance problem
๐ช 7. useCallback() โ Stabilize Functions
Consider:
function Parent() {
const handleClick = () => {
console.log("clicked");
};
return <Child onClick={handleClick} />;
}
Every parent render creates a new function.
render #1 โ function A
render #2 โ function B
render #3 โ function C
useCallback() can preserve the function reference:
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
Now React can reuse the same function reference.
But remember:
useCallback() isnโt automatically an optimization.
Use it primarily when:
- Passing callbacks to memoized children
- Function identity matters
- Dependencies are expensive to recreate
- Profiling indicates unnecessary renders
๐ก๏ธ 8. React.memo() โ Prevent Unnecessary Child Renders
const UserCard = React.memo(function UserCard({ user }) {
return <h2>{user.name}</h2>;
});
If the parent renders but user remains referentially equal, React can skip rendering UserCard.
Powerful combination
const handleDelete = useCallback((id) => {
deleteUser(id);
}, []);
const UserCard = React.memo(({ user, onDelete }) => {
return (
<button onClick={() => onDelete(user.id)}>
Delete
</button>
);
});
Here:
React.memo
+
useCallback
โ
Stable child props
โ
Fewer renders
But donโt wrap every component with React.memo() blindly.
๐งฌ 9. Referential Equality Matters
React frequently compares values by reference.
const user1 = { name: "John" };
const user2 = { name: "John" };
console.log(user1 === user2);
// false
Even though their content is identical.
This matters for:
React.memouseMemouseCallback- dependency arrays
- context
- state updates
Example
โ
<Child options= />
A new object is created every render.
Better:
const options = useMemo(
() => ({ darkMode: true }),
[]
);
<Child options={options} />
Again, only do this when the stable reference actually matters.
๐งฑ 10. Donโt Mutate State
โ Wrong
user.name = "Lakhveer";
setUser(user);
React may not detect the change correctly because the reference remains the same.
โ Correct
setUser(prev => ({
...prev,
name: "Lakhveer"
}));
For arrays:
โ
items.push(newItem);
setItems(items);
โ
setItems(prev => [...prev, newItem]);
Golden rule
Treat React state as immutable.
๐ 11. Use Stable Keys
โ
users.map((user, index) => (
<User key={index} user={user} />
))
Using indexes can cause problems when:
- Items are reordered
- Items are inserted
- Items are deleted
โ
users.map(user => (
<User key={user.id} user={user} />
))
Keys help React understand:
Old UI
โ
New UI
โ
Which item changed?
Which item moved?
Which item disappeared?
๐ฆ 12. Lazy Load Heavy Components
Donโt send everything to the browser immediately.
const AdminDashboard = lazy(
() => import("./AdminDashboard")
);
Then:
<Suspense fallback={<Loading />}>
<AdminDashboard />
</Suspense>
Instead of:
Initial Bundle
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
you can create:
Initial Bundle
โโโโโโโโ
Admin Dashboard
โโโโโโโ
Reports
โโโโโโโ
This reduces initial JavaScript.
๐งญ 13. Route-Based Code Splitting
Large applications should split code by route.
Example:
const Dashboard = lazy(
() => import("./pages/Dashboard")
);
const Reports = lazy(
() => import("./pages/Reports")
);
const Settings = lazy(
() => import("./pages/Settings")
);
Users shouldnโt download the Reports page when theyโre visiting Settings.
๐ผ๏ธ 14. Optimize Images
Images are often bigger performance killers than React itself.
Use:
- WebP
- AVIF
- Responsive images
- Proper dimensions
- Compression
- Lazy loading
<img
src="/product.webp"
alt="Product"
loading="lazy"
width="400"
height="300"
/>
Donโt send a 4000ร3000 image when you display it at 400ร300.
๐ง 15. Avoid Prop Drilling
โ
App
โ
Dashboard
โ
Sidebar
โ
Menu
โ
Profile
Passing:
user={user}
through every layer becomes difficult to maintain.
Possible solutions:
- Context
- State management library
- Composition
- Custom hooks
- External stores
But donโt introduce global state just to avoid passing one prop.
๐ 16. Use Context Carefully
Context is useful for:
- Theme
- Authentication
- Locale
- Global configuration
But context updates can cause all consumers to re-render.
โ
<AuthContext.Provider
value=
>
The object can be recreated every render.
Better
const value = useMemo(
() => ({ user, login, logout }),
[user, login, logout]
);
For very large applications, split contexts:
AuthContext
ThemeContext
CartContext
NotificationContext
instead of one giant:
GlobalContext
๐ช 17. Custom Hooks = Reusable Logic
Instead of repeating logic:
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] =
useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
Usage:
const debouncedSearch = useDebounce(search, 500);
This keeps components focused on UI.
๐ 18. Debounce Expensive User Actions
Search boxes are a classic example.
โ
L โ API
La โ API
Lak โ API
Lakh โ API
Lakhv โ API
Lakhveer โ API
Potentially 7 requests.
โ Debounce
L
La
Lak
Lakh
Lakhveer
โ
Wait 500ms
โ
API request
This reduces:
- API calls
- CPU usage
- Server load
- UI noise
๐ฆ 19. Throttle High-Frequency Events
Some events fire continuously:
scroll
mousemove
resize
touchmove
Use throttling when you need periodic updates.
function throttle(fn, delay) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn(...args);
}
};
}
Instead of processing 200 events per second:
200 events
โ
Throttle
โ
10 meaningful updates
โก 20. Use Functional State Updates
When new state depends on previous state:
โ
setCount(count + 1);
Better
setCount(prev => prev + 1);
Especially when multiple updates happen:
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
Result:
+3
๐งฎ 21. Avoid Expensive Work During Render
โ
function ProductList({ products }) {
const sorted = products
.sort(expensiveSortFunction);
return <List products={sorted} />;
}
Problems include:
- Sorting every render
- Mutating the original array
Better
const sorted = useMemo(() => {
return [...products].sort(expensiveSortFunction);
}, [products]);
๐ 22. Virtualize Huge Lists
Rendering 10,000 elements is expensive.
โ
users.map(user => (
<UserCard key={user.id} user={user} />
))
Instead, virtualization renders only whatโs visible.
10,000 records
โ
Virtualized List
โ
~20 visible rows
Popular approaches include:
react-windowTanStack Virtual
This can dramatically improve large tables and feeds.
๐ง 23. Use useReducer() for Complex State
If state transitions become complicated:
const [state, dispatch] = useReducer(
reducer,
initialState
);
Example:
function reducer(state, action) {
switch (action.type) {
case "ADD":
return {
...state,
count: state.count + 1
};
case "RESET":
return initialState;
default:
return state;
}
}
Better than having:
setLoading(...)
setError(...)
setData(...)
setStatus(...)
setMessage(...)
scattered everywhere.
๐งต 24. Keep Expensive Updates Non-Urgent
Modern React provides concurrency-oriented APIs such as:
startTransition(() => {
setSearchResults(results);
});
This tells React that certain updates can be treated as less urgent.
A useful mental model:
User typing
โ
HIGH PRIORITY
โ
Keep UI responsive
Filtering 10,000 items
โ
LOWER PRIORITY
โ
Can be interrupted
๐ 25. useDeferredValue()
Useful when displaying expensive derived UI.
const deferredSearch = useDeferredValue(search);
You can keep the input responsive while expensive UI catches up.
search
โ
Immediate UI
deferredSearch
โ
Expensive results
๐งช 26. Measure Before Optimizing
One of the biggest developer mistakes:
Optimizing code that isnโt slow.
Use profiling tools.
Look for:
Component
โโโ Render count
โโโ Render duration
โโโ Why did it render?
โโโ Expensive calculation
โโโ Large component tree
Useful tools include:
- React DevTools Profiler
- Browser Performance panel
- Lighthouse
- Chrome Memory tools
- Network panel
Golden rule:
Measure
โ
Identify bottleneck
โ
Optimize
โ
Measure again
Not:
useMemo everywhere
โ
useCallback everywhere
โ
hope it's faster
๐ 27. Identify Unnecessary Re-renders
A common symptom:
Parent renders
โ
Child renders
โ
Grandchild renders
โ
Huge table renders
Even though only a tiny part changed.
Ask:
๐ Checklist
1. Did the parent render?
2. Did props change?
3. Did object references change?
4. Did function references change?
5. Did context change?
6. Did local state change?
7. Is the component actually expensive?
This gives you the root cause instead of blindly adding memoization.
๐จ 28. Avoid the โGod Componentโ
A component like:
App.jsx
with:
2000 lines
50 states
20 effects
30 callbacks
15 API calls
is a maintenance disaster.
Break it into:
components/
hooks/
services/
utils/
features/
pages/
A useful structure:
src/
โโโ components/
โโโ features/
โ โโโ users/
โ โโโ products/
โ โโโ orders/
โโโ hooks/
โโโ services/
โโโ utils/
โโโ pages/
โโโ app/
๐ 29. Donโt Put Secrets in React
Never do:
const API_KEY = "secret-key";
Anything shipped to the browser can potentially be inspected.
Remember:
Frontend
โ
Public
Secrets belong on the server.
๐ 30. Optimize API Requests
Avoid:
Component A โ API
Component B โ API
Component C โ API
when all three request the same data independently.
Use a server-state/data-fetching strategy where appropriate.
Modern applications commonly use tools such as:
- TanStack Query
- SWR
- Apollo Client
These can provide:
- Caching
- Deduplication
- Background refetching
- Retry
- Loading states
- Error handling
๐งน 31. Cancel Outdated Requests
Imagine:
User types:
React
ReactJS
ReactJS Performance
Request 1 may finish after request 3.
That can produce stale UI.
Use AbortController:
useEffect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${query}`, {
signal: controller.signal
});
return () => controller.abort();
}, [query]);
Now outdated requests can be cancelled.
๐ฏ 32. Donโt Fetch Data You Donโt Need
Bad:
GET /users
returning:
{
"id": 1,
"name": "...",
"email": "...",
"address": "...",
"orders": [],
"payments": [],
"permissions": [],
"history": []
}
when your screen needs only:
{
"id": 1,
"name": "Lakhveer"
}
Optimize the data boundary.
๐ฆ 33. Tree Shaking & Bundle Size
Your application can become slow even if React rendering is perfect.
Check:
JavaScript bundle
CSS
Images
Fonts
Third-party libraries
Avoid importing huge libraries for tiny functionality.
Example
Instead of importing an entire utility library for one function, prefer a targeted import when the library supports it.
Analyze your production bundle using your build tooling.
๐ง 34. Donโt Overuse Third-Party Libraries
Before installing:
npm install some-library
ask:
Can I solve this cleanly with the platform or React itself?
Adding a library introduces:
- Bundle size
- Dependency maintenance
- Security considerations
- Upgrade costs
- Complexity
The best dependency is often the dependency you donโt need.
๐งฑ 35. Prefer Composition Over Giant Configuration
Instead of:
<Modal
showHeader
showFooter
showClose
showActions
showIcon
...
/>
consider composition:
<Modal>
<Modal.Header />
<Modal.Body>
Content
</Modal.Body>
<Modal.Footer>
<Button>Save</Button>
</Modal.Footer>
</Modal>
This often creates more flexible APIs.
๐จ 36. Donโt Optimize JSX at the Cost of Readability
โ Clever but difficult
{condition && data?.items?.length &&
data.items.map(...)}
Better
const hasItems = data?.items?.length > 0;
if (!hasItems) {
return <EmptyState />;
}
return <ItemList items={data.items} />;
Performance matters.
But maintainability is also performanceโfor your future self and your team.
๐ง 37. Avoid Derived State
Instead of:
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
do:
const [products, setProducts] = useState([]);
const [search, setSearch] = useState("");
const filteredProducts = useMemo(() => {
return products.filter(product =>
product.name.includes(search)
);
}, [products, search]);
One source of truth.
๐ 38. Use the Right Optimization at the Right Problem
| Problem | Solution |
|---|---|
| Expensive calculation | useMemo |
| Unstable callback | useCallback |
| Expensive child render | React.memo |
| Huge list | Virtualization |
| Large initial bundle | Lazy loading |
| Search API spam | Debounce |
| Scroll spam | Throttle |
| Complex state | useReducer |
| Shared global data | Context/store |
| Server state | Query/cache library |
| Expensive non-urgent update | startTransition |
| Slow derived UI | useDeferredValue |
| Huge images | Image optimization |
| Repeated API calls | Caching/deduplication |
๐ 39. Common React Mistakes to Avoid
โ Mistake #1 โ useEffect() everywhere
useEffect(() => {
setFullName(first + last);
}, [first, last]);
Use derived values instead.
โ Mistake #2 โ Memoizing everything
useMemo(...)
useMemo(...)
useCallback(...)
useCallback(...)
React.memo(...)
React.memo(...)
More optimization โ more performance.
โ Mistake #3 โ Index as key
key={index}
Use stable IDs.
โ Mistake #4 โ Mutating state
array.push(item);
Use immutable updates.
โ Mistake #5 โ Giant components
Split responsibilities.
โ Mistake #6 โ Global state for everything
Not every piece of state needs Redux/Zustand/Context.
โ Mistake #7 โ Fetching inside every component
Design a proper server-state strategy.
โ Mistake #8 โ Ignoring bundle size
A fast component inside a 10 MB JavaScript bundle isnโt really fast.
โ Mistake #9 โ Unoptimized images
Large images can destroy page performance.
โ Mistake #10 โ Premature optimization
Donโt optimize based on assumptions.
Measure first.
๐ต๏ธ 40. How to Identify React Performance Problems
When an application becomes slow, investigate in this order:
๐ Slow App
โ
โโโโโโโโโโโโโดโโโโโโโโโโโโ
โ โ
Network Rendering
โ โ
API latency Re-renders
Large payload Large lists
Duplicate calls Expensive JSX
โ โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
JavaScript
โ
Large bundles
Expensive work
โ
DOM
โ
Too many nodes
Then investigate:
๐ Network
- Are requests duplicated?
- Are responses huge?
- Are APIs slow?
- Are requests sequential unnecessarily?
๐ Rendering
- Which component renders?
- How frequently?
- Why?
๐ JavaScript
- Expensive calculations?
- Large dependencies?
- Large bundle?
๐ DOM
- Thousands of nodes?
- Complex layout?
- Expensive animations?
๐งช 41. A Practical Optimization Workflow
Use this process:
Step 1 โ Reproduce
Find the exact slow interaction.
Step 2 โ Measure
Use:
React Profiler
Chrome Performance
Network panel
Lighthouse
Step 3 โ Identify
Find the actual bottleneck.
Step 4 โ Fix architecture
Before adding memoization, ask:
Can I prevent this component from rendering?
Step 5 โ Optimize
Use the appropriate technique.
Step 6 โ Measure again
Verify that the change actually helped.
Step 7 โ Keep the simpler solution
If two approaches perform similarly:
Choose the easier one to maintain.
โก 42. React Performance Cheat Sheet
โ๏ธ React Optimization
โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ
โ โ โ
Render State Bundle
โ โ โ
React.memo Colocate state Lazy load
useMemo Avoid mutation Tree shake
useCallback Derived values Compress
โ โ โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ
โ
Data Layer
โ
Cache / Deduplicate
Debounce / Throttle
Cancel requests
โ
UI
โ
Virtualize lists
Optimize images
Reduce DOM
๐ 43. The Ultimate React Optimization Principles
If you remember only 15 rules, remember these:
1๏ธโฃ Keep state local
Donโt make everything global.
2๏ธโฃ Keep state minimal
Donโt store derived data.
3๏ธโฃ Avoid unnecessary effects
Effects are for synchronization.
4๏ธโฃ Donโt mutate state
Use immutable updates.
5๏ธโฃ Use stable keys
Prefer IDs over indexes.
6๏ธโฃ Split large components
Create meaningful boundaries.
7๏ธโฃ Measure before optimizing
Profiler > assumptions.
8๏ธโฃ Memoize selectively
useMemo, useCallback, and memo are toolsโnot decorations.
9๏ธโฃ Optimize network requests
Cache, deduplicate and cancel unnecessary requests.
๐ Lazy-load expensive features
Donโt ship everything upfront.
1๏ธโฃ1๏ธโฃ Virtualize huge lists
Render what users can see.
1๏ธโฃ2๏ธโฃ Optimize images
Images can dominate page weight.
1๏ธโฃ3๏ธโฃ Keep dependencies under control
Every package has a cost.
1๏ธโฃ4๏ธโฃ Separate server state from UI state
They have different lifecycles.
1๏ธโฃ5๏ธโฃ Optimize architecture before syntax
The biggest performance wins usually come from better design, not clever code.
๐ Final Thought
The best React developer isnโt the one who knows the most hooks.
Itโs the developer who understands when not to use them.
A high-performance React application isnโt created by sprinkling:
useMemo()
useCallback()
React.memo()
everywhere.
Itโs created through:
Good Architecture
โ
Minimal State
โ
Predictable Rendering
โ
Efficient Data Flow
โ
Small Bundles
โ
Optimized Network
โ
Measured Performance
โ
๐ Fast Application
โ๏ธ The ultimate rule:
First make it correct. Then make it simple. Then measure. Then optimize the bottleneck.
Thatโs how you move from React code that works โ React code that scales. ๐
© Lakhveer Singh Rajput - Blogs. All Rights Reserved.