React
React Final Project
Build a complete business dashboard with all React concepts working together in production-ready code.
Time to combine everything. Every React concept you've learned comes together in one real application. Think of this like assembling a car after learning about engines, wheels, and brakes separately. The DataFlow dashboard needs its final implementation. Your team wants a production-ready analytics platform that handles real user interactions, manages complex state, and performs like the dashboards at Linear or Notion.Project Requirements
Your dashboard must include six core sections. Each section demonstrates different React patterns you've mastered.Building the Main App
Start with the root component. This manages your entire application state and routing structure.// App.js - The main application shell
import React, { useState, useEffect, createContext } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Header from './components/Header';
import Sidebar from './components/Sidebar';
import Dashboard from './pages/Dashboard';
import Analytics from './pages/Analytics';
export const DataContext = createContext();
function App() {
const [data, setData] = useState({
revenue: 0,
users: 0,
orders: 0,
growth: 0
});
const [dateRange, setDateRange] = useState('30d');
return (
<DataContext.Provider value={{ data, setData, dateRange, setDateRange }}>
<Router>
<div className="app-layout">
<Header />
<div className="main-content">
<Sidebar />
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</div>
</div>
</Router>
</DataContext.Provider>
);
}Context API shares state across components. Router handles navigation without page reloads. The layout structure separates header, sidebar, and main content areas. Try this: Add more routes and see how single-page navigation works.
Stats Cards with Live Data
KPI cards need real interactivity. Users expect numbers that respond to filters and update smoothly.// StatsCard.js - Individual metric display
import React, { useState, useEffect } from 'react';
function StatsCard({ title, value, change, icon, color }) {
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
let start = 0;
const end = parseInt(value);
const duration = 1000;
const increment = end / (duration / 16);
const timer = setInterval(() => {
start += increment;
if (start >= end) {
setDisplayValue(end);
clearInterval(timer);
} else {
setDisplayValue(Math.floor(start));
}
}, 16);
return () => clearInterval(timer);
}, [value]);
return (
<div className="stats-card" style={{ borderTop: `3px solid ${color}` }}>
<div className="stats-header">
<span className="stats-icon">{icon}</span>
<span className="stats-title">{title}</span>
</div>
<div className="stats-value">{displayValue.toLocaleString()}</div>
<div className="stats-change" style={{
color: change >= 0 ? '#16a34a' : '#dc2626'
}}>
{change >= 0 ? '↗' : '↘'} {Math.abs(change)}%
</div>
</div>
);
}Counter animation uses setInterval with useEffect cleanup. Numbers increment smoothly from 0 to target value. Color-coded changes show positive/negative trends. Try this: Change the values and watch the animation restart.
Data Table with Filtering
Real dashboards need sortable, filterable data tables. Users expect to search, sort columns, and paginate through results.// DataTable.js - Interactive data table
import React, { useState, useMemo } from 'react';
function DataTable({ data }) {
const [sortField, setSortField] = useState('date');
const [sortDirection, setSortDirection] = useState('desc');
const [searchTerm, setSearchTerm] = useState('');
const sortedAndFilteredData = useMemo(() => {
let filtered = data.filter(item =>
item.customer.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.product.toLowerCase().includes(searchTerm.toLowerCase())
);
return filtered.sort((a, b) => {
const aVal = a[sortField];
const bVal = b[sortField];
if (sortDirection === 'asc') {
return aVal > bVal ? 1 : -1;
}
return aVal < bVal ? 1 : -1;
});
}, [data, sortField, sortDirection, searchTerm]);
const handleSort = (field) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
return (
<div className="data-table-container">
<input
type="text"
placeholder="Search transactions..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
<table className="data-table">
<thead>
<tr>
<th onClick={() => handleSort('customer')}>
Customer {sortField === 'customer' && (sortDirection === 'asc' ? '↑' : '↓')}
</th>
<th onClick={() => handleSort('amount')}>
Amount {sortField === 'amount' && (sortDirection === 'asc' ? '↑' : '↓')}
</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{sortedAndFilteredData.map((row, index) => (
<tr key={index}>
<td>{row.customer}</td>
<td>${row.amount.toLocaleString()}</td>
<td>
<span className={`status ${row.status.toLowerCase()}`}>
{row.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}useMemo optimizes expensive filtering and sorting operations. Search filters data in real-time. Column headers toggle sort direction on click. Status badges use conditional styling. Try this: Search for "Netflix" and click column headers to sort.
Custom Hooks for Logic
Complex dashboards need reusable logic. Custom hooks extract common patterns like API calls, form handling, and local storage management.// hooks/useLocalStorage.js - Persistent data hook
import { useState, useEffect } from 'react';
function useLocalStorage(key, defaultValue) {
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.warn(`Error reading localStorage key "${key}":`, error);
return defaultValue;
}
});
const setStoredValue = (newValue) => {
try {
setValue(newValue);
window.localStorage.setItem(key, JSON.stringify(newValue));
} catch (error) {
console.warn(`Error setting localStorage key "${key}":`, error);
}
};
return [value, setStoredValue];
}
// hooks/useDashboardData.js - Data fetching hook
function useDashboardData(dateRange) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
const mockData = {
revenue: Math.floor(Math.random() * 50000) + 25000,
users: Math.floor(Math.random() * 10000) + 5000,
orders: Math.floor(Math.random() * 2000) + 1000,
growth: Math.floor(Math.random() * 40) - 10
};
setData(mockData);
setError(null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [dateRange]);
return { data, loading, error };
}Custom hooks encapsulate complex logic for reuse across components. useLocalStorage persists user preferences. useDashboardData handles API calls with loading states. Both hooks manage their own state and side effects. Try this: Change date ranges and see how preferences persist on reload.
Production Best Practices
Real applications need error boundaries, performance optimization, and proper code organization. These patterns separate hobby projects from professional software.Use React.memo for expensive components. Implement useMemo for complex calculations. Add useCallback for event handlers in child components. Split code with React.lazy for route-based loading. Monitor bundle size with webpack-bundle-analyzer.
// ErrorBoundary.js - Production error handling
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Dashboard error:', error, errorInfo);
// Send to error reporting service in production
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Oops! Something went wrong</h2>
<p>The DataFlow dashboard encountered an error. Our team has been notified.</p>
<button onClick={() => window.location.reload()}>
Reload Dashboard
</button>
</div>
);
}
return this.props.children;
}
}Start with basic components and add complexity gradually. Test each feature thoroughly before moving to the next. Document your component APIs and state management decisions. Consider accessibility from the beginning, not as an afterthought. Deploy early and often to catch integration issues.
Quiz
1. What makes the DataFlow final project different from earlier mini-projects?
2. Why does the DataTable component use useMemo for filtering and sorting?
3. What role do Error Boundaries play in production React applications?