Building a React dashboard that scales well and performs efficiently requires a combination of smart coding practices, proper state management, and leveraging modern performance optimization techniques. In this chapter, we will explore strategies to improve rendering performance, minimize API calls, optimize asset loading, and prepare the dashboard for scalability.
1. Optimizing React Renders
Understanding React Re-Renders
React re-renders occur when component state or props change. While necessary for UI updates, excessive re-renders can degrade performance. The key to optimizing renders is ensuring components update only when needed.
Memoization Techniques
useMemo for Expensive Computations
useMemo caches the result of an expensive computation and recomputes only when dependencies change.
import { useMemo } from 'react';
const ExpensiveComponent = ({ data }) => {
const processedData = useMemo(() => {
return data.map(item => item * 2); // Example computation
}, [data]);
return <div>{processedData.join(', ')}</div>;
};
useCallback for Memoizing Functions
Use useCallback to prevent unnecessary function recreation.
import { useCallback } from 'react';
const Button = ({ onClick }) => {
return <button onClick={onClick}>Click Me</button>;
};
const ParentComponent = () => {
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return <Button onClick={handleClick} />;
};
React.memo for Preventing Unnecessary Renders
Wrap functional components with React.memo to avoid re-renders unless props change.
import React from 'react';
const MemoizedComponent = React.memo(({ value }) => {
console.log('Rendering...');
return <div>{value}</div>;
});
2. Lazy Loading & Code Splitting
Using React.lazy() for Dynamic Imports
Split large components into separate files and load them only when needed.
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const Dashboard = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
};
Route-Based Code Splitting with React Router
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</Router>
);
3. Reducing API Calls & Improving Caching
Using React Query for Optimized Data Fetching
Instead of refetching data on every render, use React Query to cache and revalidate API calls efficiently.
import { useQuery } from 'react-query';
import axios from 'axios';
const fetchUsers = async () => {
const { data } = await axios.get('/api/users');
return data;
};
const UserList = () => {
const { data, error, isLoading } = useQuery('users', fetchUsers, {
staleTime: 1000 * 60 * 5, // Cache data for 5 minutes
refetchOnWindowFocus: false,
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error fetching users</p>;
return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
};
Avoiding Unnecessary API Calls with Debouncing
Use lodash.debounce to delay API calls while typing.
import { useState } from 'react';
import debounce from 'lodash.debounce';
const SearchComponent = ({ fetchResults }) => {
const [query, setQuery] = useState('');
const debouncedSearch = debounce(fetchResults, 500);
const handleChange = (e) => {
setQuery(e.target.value);
debouncedSearch(e.target.value);
};
return <input type="text" value={query} onChange={handleChange} />;
};
4. Improving Load Times
Preloading Assets
Use <link rel="preload"> to load critical resources faster.
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin="anonymous">
Optimizing Bundle Size
Use vite-plugin-compression to enable Gzip compression for assets in Vite.
npm install vite-plugin-compression --save-dev
Modify vite.config.js:
import compression from 'vite-plugin-compression';
export default {
plugins: [compression()],
};
5. Server-Side Rendering (Optional)
For extremely high-performance needs, consider using Next.js for SSR.
import { useEffect, useState } from 'react';
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}
const Dashboard = ({ data }) => {
return <div>{data.length} records found.</div>;
};
Conclusion
By applying these performance optimizations, your React dashboard will render efficiently, load quickly, and handle large data sets with ease. These best practices ensure that your application remains scalable and performant even as complexity increases.