You've probably seen the same pattern already. A client wants “a dashboard” in the React app, someone pastes in a basic bar chart from a demo, and it looks fine until real ERP data shows up. Then the problems start. Labels overflow, loading states are missing, filters trigger expensive re-renders, and the chart that looked clean with mock values turns messy when Odoo starts sending uneven, noisy business data.
That gap between demo code and production code is where most chart tutorials stop being useful. Business dashboards don't exist to decorate a screen. They help an operations lead spot delayed purchase orders, a finance manager compare invoiced revenue against targets, or a warehouse team catch inventory drift before it becomes a customer issue. If the chart can't handle live API data, interaction, and performance trade-offs, it isn't ready for a business app.
Teams building data visualization dashboards usually discover this quickly. The visual itself is only one layer. Data shape, refresh behaviour, UI states, and long-term maintainability matter just as much. If you're pulling metrics from an ERP, that becomes even more obvious because the chart sits inside a bigger reporting workflow, not in isolation.
That's also why real-time reporting has to be treated as a product concern, not just a frontend task. If you need a business case for that shift, this overview of real-time business reporting benefits is useful context before you write a single component.
Table of Contents
- From Static Visuals to Dynamic Business Insights
- Choosing Your React Charting Library
- Building Your First Interactive Bar Chart
- Connecting Charts to Real-World API Data
- Optimising for Performance and Accessibility
- Advanced Techniques and Common Pitfalls
From Static Visuals to Dynamic Business Insights
A chart in a business app has a job. It should help someone decide what to do next.
In an ERP-driven React app, that usually means turning operational records into something readable at a glance. Monthly sales by region. Stock movement by warehouse. Late deliveries by carrier. Support volume by team. These aren't abstract chart examples. They come from models, endpoints, permissions, and business rules that already exist elsewhere in the system.
The real job of a chart in an ERP app
Most frontend examples start with a static array and stop there. That's fine for learning syntax, but it hides the hard parts. Production charts need to cope with missing values, inconsistent date ranges, user-selected filters, and data that arrives late or fails entirely.
Practical rule: If the user can act on the chart, build the chart around the workflow, not the visual type.
That changes how you think about charts with React. A bar chart isn't “a bar chart”. It's often a compact answer to a specific question such as which product categories are underperforming this month compared with the prior reporting window. Once you frame it that way, the component design improves. You know what data shape you need, what interaction matters, and what can be left out.
Library first or primitives first
One of the most overlooked decisions is whether to use a chart library or build from low-level primitives. The harder question isn't whether you can render an SVG path manually. It's how that choice affects performance, bundle size, and maintainability once the dashboard grows beyond a few simple charts, a trade-off highlighted in OpenReplay's discussion of low-level charts in React.
Low-level work makes sense when the visual grammar is highly custom or the product itself depends on bespoke visualisation. For a typical ERP dashboard, that's rarely the best place to start. You want predictable axes, tooltips, legends, responsiveness, and event handling without rebuilding chart infrastructure from scratch.
A good production approach usually looks like this:
- Use a library for common business visuals. Bars, lines, areas, pies, and mixed dashboards are cheaper to maintain when the rendering, scales, and interactions are already solved.
- Keep transformations outside the chart component. The chart should receive clean series data, not own all the reporting logic.
- Optimise for change. Business users always ask for one more filter, one more comparison, one more drill-down. Library-backed components tolerate that better than hand-built SVG experiments.
The shift from static visuals to dynamic business insight isn't mainly about prettier charts. It's about building a small reporting surface inside your product that can survive real usage.
Choosing Your React Charting Library
The library decision shapes more than the API you type. It affects rendering behaviour, how easy it is to debug, how much custom interaction you can add, and whether the dashboard still feels smooth after a few more charts land on the page.
React-specific chart tooling has matured. MUI describes X Charts as a collection of React chart components, and AG Charts positions its React package as a high-performance, canvas-based charting library built for React apps in the MUI React Charts documentation. That matters because chart selection in production is now tied to rendering speed, maintainability, and fit with an existing React frontend, not just visual style.
What the library choice really affects
For charts with React, I usually evaluate libraries against four practical questions:
- How quickly can the team ship the first dashboard? A declarative API helps when the team needs a result this sprint, not after building chart plumbing.
- How far can styling and interaction go? ERP dashboards often need branded themes, filter-driven updates, and custom tooltips tied to business data.
- How does it behave as the page gets denser? A chart that works fine alone can struggle in a dashboard with several cards, tables, and side panels.
- Will the next developer understand it? Maintainability wins more projects than cleverness.
If you also work across mobile products, the trade-offs are similar. The AppLighter guide on how to create professional React Native charts is worth a read because it shows the same tension between convenience, customisation, and production fit in another React environment.

React Charting Library Comparison
| Library | Primary Strength | Learning Curve | Best For |
|---|---|---|---|
| Recharts | Clean declarative API and quick dashboard delivery | Low | Internal dashboards and common business charts |
| Victory | Strong component model and styling flexibility | Medium | Teams that want more composition control |
| React Chart.js 2 | Familiar Chart.js ecosystem and broad chart coverage | Low to medium | Fast implementation of standard chart types |
| D3 | Maximum control over data visualisation behaviour | High | Custom visual systems and specialised interactions |
| AG Charts | Canvas-based performance focus in React | Medium | Data-dense apps where rendering throughput matters |
| MUI X Charts | Tight fit with MUI-based React interfaces | Low to medium | Products already using the MUI design system |
How to choose for an ERP dashboard
Recharts is often the easiest place to start. It maps well to React thinking, and the mental model is friendly for teams that already build UI components every day. If the requirement is a sales chart, inventory trend, or order-status breakdown, Recharts usually gets you there quickly.
Victory is useful when your team wants a more composable chart structure. It tends to suit developers who don't mind spending more time shaping the final result.
React Chart.js 2 works well when the team already knows Chart.js or needs a broad set of familiar chart types without much ceremony. It can be a solid practical option, though I'd still test its behaviour with your real dashboard state patterns.
D3 is powerful, but I wouldn't recommend it for a first ERP dashboard unless the visual requirement demands custom primitives. Maintenance costs are often underestimated.
Use D3 when the chart is your product. Use a higher-level library when the chart supports your product.
For heavier use cases, AG Charts deserves a serious look because of its canvas-based approach. For MUI-heavy apps, MUI X Charts can reduce friction across the UI layer. The right answer depends less on popularity and more on whether the library matches your actual reporting load and team workflow.
Building Your First Interactive Bar Chart
A first chart should answer one narrow business question well. Don't start with a dashboard grid full of mixed visuals. Start with one chart card that proves the data flow, the component structure, and the interaction pattern.
For an ERP-style example, monthly sales by category is a good candidate. Most stakeholders understand it immediately, and it maps cleanly to both static and API-driven data.

Start with a chart that answers one business question
Modern React chart libraries have become declarative enough that developers can move from raw data to dashboard components with minimal glue code, with libraries such as AG Charts showing how little more than data and series are needed in their React quick start. Even if you use Recharts instead, that same declarative mindset is what makes charts with React practical for day-to-day product work.
Install Recharts in the project and create a component that accepts a simple data array.
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer
} from 'recharts';
const salesData = [
{ category: 'Services', sales: 4200 },
{ category: 'Hardware', sales: 3100 },
{ category: 'Subscriptions', sales: 5300 },
{ category: 'Support', sales: 1900 }
];
export default function SalesByCategoryChart() {
return (
<div style={{ width: '100%', height: 320 }}>
<ResponsiveContainer>
<BarChart data={salesData} margin={{ top: 16, right: 16, left: 0, bottom: 8 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="category" />
<YAxis />
<Tooltip />
<Bar dataKey="sales" fill="#111111" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
);
}
That first pass is enough to validate layout, spacing, and whether the data shape feels sensible.
A practical Recharts example
Don't leave the component at demo level. Add a container card, an empty state, and formatting logic that reflects business meaning.
function formatCurrency(value) {
return new Intl.NumberFormat('en-GB', {
style: 'currency',
currency: 'GBP',
maximumFractionDigits: 0
}).format(value);
}
function CustomTooltip({ active, payload, label }) {
if (!active || !payload?.length) return null;
return (
<div
style={{
background: '#fff',
border: '1px solid #ddd',
padding: '10px 12px',
borderRadius: 6
}}
>
<strong>{label}</strong>
<div>Sales: {formatCurrency(payload[0].value)}</div>
</div>
);
}
Use that tooltip in the chart instead of the default one. Small touches like currency formatting do more for usability than adding extra visual effects.
A quick walkthrough helps if you want to compare your implementation with another developer's setup:
Adding tooltip and click behaviour
Interactive charts should lead somewhere. In a business app, hover states are useful, but click behaviour often matters more because it connects summary reporting to detail views.
export default function SalesByCategoryChart() {
const handleBarClick = (entry) => {
console.log('Open detail view for', entry.category);
};
return (
<div style={{ width: '100%', height: 320 }}>
<ResponsiveContainer>
<BarChart data={salesData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="category" />
<YAxis tickFormatter={formatCurrency} />
<Tooltip content={<CustomTooltip />} />
<Bar
dataKey="sales"
fill="#111111"
onClick={handleBarClick}
/>
</BarChart>
</ResponsiveContainer>
</div>
);
}
A few habits keep this maintainable:
- Keep formatting functions pure. Currency, date, and unit formatting should live outside render-heavy chart internals.
- Pass events up where possible. Let the parent decide whether a click opens an Odoo record, updates a filter, or routes to another page.
- Make the first chart boring on purpose. If a basic bar chart is unreliable, adding stacked series and drill-down won't fix the architecture.
Once this works with static data, you're ready for the part that decides whether the dashboard is production-ready.
Connecting Charts to Real-World API Data
Static arrays are useful for layout. They don't tell you whether the dashboard will hold up once the ERP starts returning real records.
The first jump in complexity comes from data lifecycle, not chart syntax. You need to fetch data, handle delays, surface errors, and transform backend records into a shape the chart can consume without turning the component into a tangle of useEffect branches.

Treat API data as a pipeline
If the source is Odoo or another ERP, don't pipe raw responses directly into chart props. Put a small transformation layer between the fetch and the visual.
That layer should answer three questions:
- What records does the API return?
- Which fields matter for the chart?
- How should nulls, missing periods, or inconsistent labels be handled?
If your team is still wiring backend and frontend together, this guide to API integration for web and mobile systems is a practical reference for structuring that boundary well.
The chart should receive reporting data, not business-system noise.
A fetch pattern that holds up in production
Here's a clean React pattern for monthly sales data.
import { useEffect, useState } from 'react';
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer
} from 'recharts';
function formatCurrency(value) {
return new Intl.NumberFormat('en-GB', {
style: 'currency',
currency: 'GBP',
maximumFractionDigits: 0
}).format(value);
}
export default function MonthlySalesChart() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
let ignore = false;
async function loadSales() {
try {
setLoading(true);
setError('');
const response = await fetch('/api/dashboard/monthly-sales');
if (!response.ok) {
throw new Error('Failed to fetch monthly sales');
}
const json = await response.json();
const transformed = json.map((item) => ({
month: item.month_name,
sales: item.total_sales
}));
if (!ignore) {
setData(transformed);
}
} catch (err) {
if (!ignore) {
setError(err.message || 'Something went wrong');
}
} finally {
if (!ignore) {
setLoading(false);
}
}
}
loadSales();
return () => {
ignore = true;
};
}, []);
if (loading) return <div>Loading chart data...</div>;
if (error) return <div role="alert">{error}</div>;
if (!data.length) return <div>No sales data available for this period.</div>;
return (
<div style={{ width: '100%', height: 320 }}>
<ResponsiveContainer>
<BarChart data={data}>
<XAxis dataKey="month" />
<YAxis tickFormatter={formatCurrency} />
<Tooltip formatter={(value) => formatCurrency(value)} />
<Bar dataKey="sales" fill="#111111" />
</BarChart>
</ResponsiveContainer>
</div>
);
}
Map ERP records into chart-ready data
Often, teams slip when they fetch raw data successfully but skip normalisation.
For example, an ERP endpoint may return months with gaps because no invoices were posted in one period. If the chart should show a continuous business timeline, fill those gaps before rendering. If category names differ because users entered inconsistent labels, normalise them in the API layer or transformation function.
A dependable pattern looks like this:
- Fetch raw records from the reporting endpoint.
- Transform into chart fields such as
label,value,series, ordate. - Normalise gaps and sort order before passing data to the chart.
- Render explicit states for loading, errors, and empty responses.
That discipline makes charts with React much easier to reason about. The chart component stops being responsible for everything and becomes a stable presentation layer over reporting data.
Optimising for Performance and Accessibility
A chart that renders correctly can still be the slowest and least usable part of the page. That usually happens when teams treat performance and accessibility as finishing touches.
For dashboard work, they belong in the first implementation pass. Not because every chart is huge, but because performance issues often come from repeated transforms, excessive React state churn, and interaction code that looked harmless during development.
Reduce work before you reach for a faster library
Start with the obvious wins. Memoise expensive transforms, debounce filter-driven updates, and avoid rebuilding large data arrays on every render.
import { useMemo } from 'react';
function SalesChartCard({ records, selectedWarehouse }) {
const chartData = useMemo(() => {
return records
.filter((row) => !selectedWarehouse || row.warehouse === selectedWarehouse)
.map((row) => ({
label: row.month,
value: row.total
}));
}, [records, selectedWarehouse]);
return <SalesChart data={chartData} />;
}
That won't fix every performance problem, but it removes a lot of accidental work.

Other practical steps matter just as much:
- Lazy load chart-heavy routes. Dashboards don't have to inflate the initial bundle for unrelated screens.
- Pre-aggregate on the server. Send reporting-ready data instead of every raw transaction when the user only needs summaries.
- Limit real-time updates to visible scope. Don't repaint a full dashboard if only one panel changes.
Choose rendering strategy with intent
For demanding dashboards, rendering approach becomes a significant architectural choice. SciChart states its React charts are designed for real-time data, dashboards, and large datasets, including support for millions of data points on its React charts page. That's a useful benchmark because the key question under load is whether the chart stack can handle million-point workloads without frame drops.
If the use case involves dense operational streams, a practical pattern is to pre-aggregate historical data server-side, downsample time-series before they hit the browser, and keep high-frequency updates focused on the visible window. That reduces main-thread pressure and keeps interaction stable.
SVG-first libraries are often excellent for moderate dashboard use. They're also easier to inspect and style. But once point counts rise or updates become frequent, canvas-based or high-throughput chart stacks usually make more sense.
Heavy charts fail long before the browser crashes. They fail when users stop trusting the dashboard because it feels unstable.
If accessibility and usability are part of your broader frontend quality work, this overview of UI and UX design practices is a useful companion to chart-specific decisions.
Accessibility work that teams skip too often
Most chart accessibility issues aren't caused by the library. They come from how the team embeds the chart in the page.
A few baseline habits go a long way:
- Give the chart context. Add a visible title and short supporting text so screen-reader users know what the visual represents.
- Provide a text fallback. A small data summary or table helps users who can't rely on hover interactions.
- Don't use colour alone. If series distinction matters, combine colour with labels, markers, or pattern differences.
- Support keyboard access where interaction exists. If users can inspect, zoom, or select, those actions shouldn't require a mouse.
- Use meaningful ARIA labels on wrapper elements. The chart container should announce purpose, not just existence.
Responsive behaviour belongs here too. Tooltips that work on desktop can become frustrating on small screens. Dense legends can crush the plot area. Always test with reduced width before shipping.
Advanced Techniques and Common Pitfalls
As soon as the dashboard has more than one chart, the problem changes. You're no longer building a chart component. You're building a reporting system inside the app.
That means shared filters, coordinated date ranges, cache behaviour, cross-chart drill-down, and tests for the logic that shapes the data before it ever reaches the visual layer.
Shared state across multiple charts
If several charts respond to the same warehouse, sales team, or date filter, keep that state above the individual chart components. The chart should receive inputs and emit events, not own global dashboard state.
In practice, that usually means one parent container or dashboard store handles:
- Active filters such as date range, company, warehouse, or salesperson
- Fetched reporting datasets that can be reused across cards
- Selection events when a chart click narrows another widget
- Refresh rules so all charts don't refetch unnecessarily
This becomes even more important when the library supports rich interaction. Syncfusion's React Charts offers 55+ chart types and built-in controls such as zooming, panning, tooltips, crosshairs, trackball, highlighting, and selection on its React Charts product page. That kind of breadth is useful for drill-down workflows, but only if the dashboard state model stays organised.
Test the data logic, not just the pixels
The hardest chart bugs usually come from transformation code. A quarter label sorts incorrectly. Empty months disappear. Credits and refunds are merged into revenue without the right rule.
Test those parts directly.
- Unit test transformation functions. Given raw ERP records, assert the output matches the reporting shape the chart expects.
- Add visual regression checks for chart containers. Catch broken layout, clipped labels, or theme regressions before a release.
- Exercise loading and empty states. Those states appear often in business software and are easy to neglect.
I'd rather trust a simple chart with tested data mapping than a complex chart with fragile assumptions.
A short pre-launch checklist
Before shipping charts with React into a production ERP environment, check the basics:
- Bundle weight: Did you pull in a heavy library for one small visual?
- Client-side overload: Are you trying to render raw operational history instead of pre-aggregated summaries?
- Missing interactions: If users need drill-down, have you given them clicks, zooming, or filters that support action?
- Weak empty states: Does the card explain whether there's no data, delayed data, or a failed request?
- No fallback context: Can users still understand the metric without relying only on hover?
A dashboard earns trust slowly and loses it quickly. Reliable charts, clear interaction, and disciplined data handling are what keep that trust intact.
If your team is building ERP dashboards that need to pull live Odoo data into fast, maintainable React interfaces, ERP Artists can help with the full stack behind it. That includes ERP data modelling, API integration, frontend dashboard implementation, and the UX decisions that make reporting screens usable for real operations teams.