Master React Optimization Technique
Arkajit Roy
Posted on May 18, 2024
In today's fast-paced web landscape, a React application's performance is paramount. A sluggish app can lead to frustrated users and lost conversions. Fortunately, React provides a plethora of built-in features and libraries to streamline your application and deliver a seamless experience. Here are 7 optimization techniques that will elevate your React app to production-grade quality
01. Component Memoization
To achieve memoization we can employee React.memo which is a higher-order component (HOC) that prevents unnecessary re-renders of functional components by memoizing the component output based on its props. It re-renders only if the props or the component's state change.
import React from 'react';
const MyComponent = React.memo(({ value }) => {
console.log('Rendered');
return <div>{value}</div>;
});
export default MyComponent;
Using useCallback and useMemo for Stable Reference
Similar to useMemo but for functions, useCallback prevents unnecessary function recreation when its dependencies haven't changed. This is particularly helpful for callback functions passed as props.
import React, { useCallback, useMemo, useState } from 'react';
const MyComponent = ({ items }) => {
const [count, setCount] = useState(0);
const calculateTotal = useMemo(() => {
return items.reduce((total, item) => total + item.price, 0);
}, [items]);
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
return (
<div>
<p>Total: {calculateTotal}</p>
<button onClick={increment}>Increment: {count}</button>
</div>
);
};
export default MyComponent;
02. Code Splitting Technique
Lazy loading allows you to load components or modules only when they're needed. This reduces the initial bundle size and improves load times, especially for complex applications. React's built-in React.lazy and Suspense components facilitate this process.
import React, { lazy, Suspense } from 'react';
const MyLazyComponent = lazy(() => import('./MyLazyComponent'));
function MyComponent() {
return (
<div>
<button onClick={() => import('./MyLazyComponent')}>Load Lazy Component</button>
<Suspense fallback={<div>Loading...</div>}>
<MyLazyComponent />
</Suspense>
</div>
);
}
03. Efficient Event Handling
For frequently triggered events, consider Throttling or debouncing techniques to reduce the number of function calls. Throttling ensures the function executes at most once within a specified time interval, while debouncing only executes it after a period of inactivity.
Debouncing
import React, { useState } from 'react';
import { debounce } from 'lodash';
const Search = () => {
const [query, setQuery] = useState('');
const handleSearch = debounce((event) => {
setQuery(event.target.value);
// Perform search operation
}, 300);
return <input type="text" onChange={handleSearch} />;
};
export default Search;
Throttling
import React, { useEffect, useRef } from 'react';
const AnimationComponent = () => {
const ref = useRef();
useEffect(() => {
const animate = () => {
// Update animation frame
requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
}, []);
return <div ref={ref}>Animating...</div>;
};
export default AnimationComponent;
04. Rendering Long Lists (Virtualized Lists)
When dealing with extensive lists, list virtualization becomes crucial. It renders only the visible items on the screen, significantly improving performance and reducing DOM manipulation. Popular libraries like react-window and react-virtualized offer efficient solutions
import React from 'react';
import { FixedSizeList as VirtualizedList } from 'react-window';
const ListItem = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
const MyList = ({ items }) => (
<VirtualizedList
height={150}
itemCount={items.length}
itemSize={35}
width={300}
>
{ListItem}
</VirtualizedList>
);
export default MyList;
05. Track Performance Bottlenecks
Profiling is the cornerstone of optimization. Use React DevTools Profiler to identify performance bottlenecks in your components. It pinpoints areas consuming excessive rendering time, guiding your optimization efforts.
import React, { Profiler } from 'react';
const onRenderCallback = (
id, // the "id" prop of the Profiler tree that has just committed
phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
actualDuration, // time spent rendering the committed update
baseDuration, // estimated time to render the entire subtree without memoization
startTime, // when React began rendering this update
commitTime, // when React committed this update
interactions // the Set of interactions belonging to this update
) => {
console.log('Render time:', actualDuration);
};
const App = () => (
<Profiler id="App" onRender={onRenderCallback}>
<MyComponent />
</Profiler>
);
export default App;
06. Optimize Context Usage
Using useContextSelector from the use-context-selector library can help in avoiding unnecessary re-renders when using context.
import React from 'react';
import { useContextSelector } from 'use-context-selector';
const CountContext = React.createContext();
const Display = () => {
const count = useContextSelector(CountContext, state => state.count);
return <div>{count}</div>;
};
const App = () => {
const [count, setCount] = React.useState(0);
const contextValue = { count, setCount };
return (
<CountContext.Provider value={contextValue}>
<Display />
<button onClick={() => setCount(count + 1)}>Increment</button>
</CountContext.Provider>
);
};
export default App;
07. Usage of Fragment Components
Avoid unnecessary DOM nodes by wrapping JSX elements with React.Fragment. This prevents creating extra
elements for layout purposes.import React, { Fragment } from 'react';
function MyComponent() {
return (
<Fragment>
<p>Item 1</p>
<p>Item 2</p>
</Fragment>
);
}
// OR
function MyComponent() {
return (
<>
<p>Item 1</p>
<p>Item 2</p>
</>
);
}
Posted on May 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.