DEV Community

Ansh Sheladiya
Ansh Sheladiya

Posted on

Building Dashboards with React: A Practical Guide to Reusable Components and Data Visualization

Dashboards are often the first place users go when they want to understand what is happening in an application. Whether you are building an analytics platform, an admin panel, or a business reporting tool, a well-designed dashboard should turn raw data into useful information without overwhelming the user.

React makes dashboard development easier by breaking complex interfaces into reusable components. Instead of building one massive page, you can separate metric cards, charts, tables, filters, and navigation into independent components that are easier to maintain and test.

In this article, we will build a small dashboard using plain JavaScript and React-inspired component patterns. We will focus on organizing data, calculating meaningful metrics, handling filtering, and preparing the foundation for a dashboard that can grow with your application.

Designing a Maintainable React Dashboard Architecture

The first step in building a reliable dashboard is separating data processing from presentation. A common mistake is placing API requests, calculations, filtering logic, and rendering conditions inside a single component. This might work for a prototype, but it quickly becomes difficult to maintain as the number of widgets and business requirements increases.

A better approach is to organize the dashboard around reusable components and predictable data flows. For example, metric cards should receive their values through props, charts should receive prepared datasets, and tables should handle displaying records rather than calculating business metrics themselves. This separation makes individual components easier to reuse across different pages.

Data preparation deserves particular attention because dashboards frequently display derived values such as total revenue, average order value, conversion rates, and month-over-month growth. These calculations should live in focused utility functions that can be tested independently. In a production application, the same functions should also account for missing values, invalid API responses, currency formatting, and empty datasets.

The following runnable JavaScript example demonstrates these principles without requiring external dependencies. It simulates dashboard data, calculates key performance indicators, filters recent orders, and prints a readable dashboard summary to the console. The functions can later be reused inside React components, custom hooks, or a dedicated dashboard data layer.

/*
 * Building Dashboards with React: Dashboard Data Layer
 *
 * Run with Node.js:
 * node dashboard.js
 *
 * This example demonstrates reusable dashboard logic that can
 * later be connected to React components and API responses.
 */

console.log("Step 1: Initializing dashboard data...");

const orders = [
  { id: 101, customer: "Aarav", amount: 1200, status: "completed", month: "2026-10" },
  { id: 102, customer: "Diya", amount: 2400, status: "completed", month: "2026-10" },
  { id: 103, customer: "Kabir", amount: 800, status: "pending", month: "2026-10" },
  { id: 104, customer: "Meera", amount: 3200, status: "completed", month: "2026-09" },
  { id: 105, customer: "Ishaan", amount: 1600, status: "cancelled", month: "2026-09" },
  { id: 106, customer: "Riya", amount: 2800, status: "completed", month: "2026-10" },
  { id: 107, customer: "Dev", amount: 950, status: "pending", month: "2026-09" }
];

console.log(`Loaded ${orders.length} orders successfully.`);

// Keep only records that contain valid numeric amounts.
function getValidOrders(records) {
  return records.filter((order) =>
    Number.isFinite(order.amount) && order.amount >= 0
  );
}

console.log("Step 2: Validating order records...");
const validOrders = getValidOrders(orders);
console.log(`Valid records: ${validOrders.length}`);

// Calculate revenue using completed orders only.
function calculateRevenue(records) {
  return records.reduce((total, order) => {
    return order.status === "completed" ? total + order.amount : total;
  }, 0);
}

// Count orders by status without mutating the original dataset.
function calculateOrderCounts(records) {
  return records.reduce((counts, order) => {
    counts[order.status] = (counts[order.status] || 0) + 1;
    return counts;
  }, { completed: 0, pending: 0, cancelled: 0 });
}

// Calculate the average value of completed orders safely.
function calculateAverageOrderValue(records) {
  const completedOrders = records.filter(
    (order) => order.status === "completed"
  );

  if (completedOrders.length === 0) {
    return 0;
  }

  return calculateRevenue(completedOrders) / completedOrders.length;
}

console.log("Step 3: Calculating key performance indicators...");
const revenue = calculateRevenue(validOrders);
const orderCounts = calculateOrderCounts(validOrders);
const averageOrderValue = calculateAverageOrderValue(validOrders);

// Return reusable data for metric cards and charts.
function buildDashboardMetrics(records) {
  const counts = calculateOrderCounts(records);
  const totalOrders = records.length;
  const completedOrders = counts.completed;

  return {
    totalOrders,
    completedOrders,
    pendingOrders: counts.pending,
    cancelledOrders: counts.cancelled,
    revenue: calculateRevenue(records),
    averageOrderValue: calculateAverageOrderValue(records),
    completionRate: totalOrders === 0
      ? 0
      : (completedOrders / totalOrders) * 100
  };
}

const metrics = buildDashboardMetrics(validOrders);
console.log("Dashboard metrics calculated:", metrics);

// Prepare monthly revenue data for a future chart component.
function buildMonthlyRevenue(records) {
  const monthlyRevenue = {};

  records.forEach((order) => {
    if (order.status !== "completed") {
      return;
    }

    monthlyRevenue[order.month] =
      (monthlyRevenue[order.month] || 0) + order.amount;
  });

  return Object.entries(monthlyRevenue)
    .sort(([monthA], [monthB]) => monthA.localeCompare(monthB))
    .map(([month, total]) => ({ month, revenue: total }));
}

console.log("Step 4: Preparing chart data...");
const chartData = buildMonthlyRevenue(validOrders);
console.log("Monthly revenue dataset:", chartData);

// Filter records based on the status selected in a dashboard control.
function filterOrdersByStatus(records, status = "all") {
  if (status === "all") {
    return [...records];
  }

  return records.filter((order) => order.status === status);
}

console.log("Step 5: Applying a dashboard filter...");
const completedOrderList = filterOrdersByStatus(validOrders, "completed");
console.log(`Completed orders displayed: ${completedOrderList.length}`);

// Format currency consistently before displaying it in the interface.
function formatCurrency(amount, locale = "en-IN", currency = "INR") {
  return new Intl.NumberFormat(locale, {
    style: "currency",
    currency,
    maximumFractionDigits: 2
  }).format(amount);
}

// Keep the console renderer independent from calculations and filtering.
function renderDashboardSummary(data) {
  console.log("\n========== SALES DASHBOARD ==========");
  console.log(`Total orders: ${data.totalOrders}`);
  console.log(`Completed orders: ${data.completedOrders}`);
  console.log(`Pending orders: ${data.pendingOrders}`);
  console.log(`Cancelled orders: ${data.cancelledOrders}`);
  console.log(`Total revenue: ${formatCurrency(data.revenue)}`);
  console.log(`Average order value: ${formatCurrency(data.averageOrderValue)}`);
  console.log(`Completion rate: ${data.completionRate.toFixed(1)}%`);
  console.log("=====================================\n");
}

console.log("Step 6: Rendering the dashboard summary...");
renderDashboardSummary(metrics);

// Export these functions when adapting the example to a module-based app.
console.log("Step 7: Dashboard processing completed successfully.");
console.log("Ready to connect these utilities to React components.");
Enter fullscreen mode Exit fullscreen mode

Conclusion

Building a dashboard with React is not just about arranging cards and charts on a page. The real challenge is creating a clear separation between data fetching, business calculations, component rendering, and user interactions so that each part remains easy to understand as the application grows.

Start with a small set of reusable components, define a consistent data structure, and keep calculations in independently testable utility functions. As requirements expand, introduce loading and error states, pagination, accessible filters, and appropriate caching rather than adding more responsibilities to a single component.

For production dashboards, consider React Query or TanStack Query for server-state management and a charting library such as Recharts for visualizations. With a predictable architecture and thoughtful data handling, you can build dashboards that remain responsive, maintainable, and useful as your application scales.

What is the biggest challenge you have faced when building a React dashboard: managing API data, optimizing performance, or designing reusable components?

Top comments (0)