React + Chart.js Data Visualization Tutorial: From Setup to Advanced Interactions
Build polished, high‑performance charts in React with Chart.js: install, register, time scales, zoom/pan, gradients, performance, and common fixes.
Image used for representation purposes only.
Overview
React pairs beautifully with Chart.js to deliver fast, flexible, and good‑looking charts with minimal code. In this tutorial you’ll set up Chart.js in a modern React app, draw your first charts, wire up a time series with a date adapter, add zooming and panning, and finish with performance tips and common fixes. We’ll use the official React wrapper, react-chartjs-2, which supports Chart.js v4 and provides convenient typed components like Line and Bar. (react-chartjs-2.js.org )
Prerequisites
- Familiarity with React hooks (useState, useEffect, useRef)
- Node.js and a React app created with your favorite tool (Vite, Create React App, Next.js, etc.)
Install and scaffold
Install Chart.js and the React wrapper:
# npm
yarn add chart.js react-chartjs-2
# or
npm install chart.js react-chartjs-2
react-chartjs-2 exposes prebuilt components and expects you to register the Chart.js elements, scales, and plugins you use. This keeps bundles lean and enables tree‑shaking. (react-chartjs-2.js.org )
First chart (Bar)
Create a basic Bar chart by importing only what you need and registering those pieces with Chart.js:
// src/components/SalesBar.tsx
import React from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
ChartOptions,
ChartData
} from 'chart.js';
import { Bar } from 'react-chartjs-2';
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
const options: ChartOptions<'bar'> = {
responsive: true,
plugins: {
legend: { position: 'top' },
title: { display: true, text: 'Quarterly Sales' }
}
};
const data: ChartData<'bar'> = {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [
{ label: '2026', data: [120, 150, 180, 210], backgroundColor: '#4F46E5' }
]
};
export default function SalesBar() {
return <Bar options={options} data={data} />;
}
That’s all you need for a simple, responsive chart.
What to register (and why tree‑shaking matters)
Chart.js v3+ split chart types into controllers, elements, scales, and plugins. Only the pieces you register are included, which improves performance and bundle size. If you prefer not to register individually while prototyping, you can import ‘chart.js/auto’ for a one‑line “lazy” setup—but registering explicitly is the recommended, tree‑shakable path. (react-chartjs-2.js.org )
Time series line chart with a date adapter
Time axes in Chart.js require a date adapter (Moment, Luxon, date‑fns, etc.). The adapter provides parsing/formatting so the time scale can plot your dates correctly. Here we’ll use chartjs-adapter-date-fns. (chartjs.org )
Install and wire the adapter:
npm install chartjs-adapter-date-fns date-fns
// src/components/TrafficLine.tsx
import React from 'react';
import 'chartjs-adapter-date-fns';
import {
Chart as ChartJS,
TimeScale,
LinearScale,
PointElement,
LineElement,
Tooltip,
Legend,
Title,
ChartOptions,
ChartData
} from 'chart.js';
import { Line } from 'react-chartjs-2';
ChartJS.register(TimeScale, LinearScale, PointElement, LineElement, Tooltip, Legend, Title);
const options: ChartOptions<'line'> = {
responsive: true,
parsing: false, // keep data as-is for performance on large series
scales: {
x: {
type: 'time',
time: { unit: 'day', tooltipFormat: 'PP' }
},
y: { ticks: { callback: (v) => `${v}k` } }
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: (ctx) => `Visits: ${new Intl.NumberFormat().format(Number(ctx.parsed.y))}`
}
}
}
};
const data: ChartData<'line'> = {
datasets: [
{
label: 'Daily Visits',
borderColor: '#10B981',
backgroundColor: 'rgba(16, 185, 129, 0.2)',
tension: 0.3,
data: [
{ x: new Date('2026-04-10'), y: 12 },
{ x: new Date('2026-04-11'), y: 18 },
{ x: new Date('2026-04-12'), y: 17 },
{ x: new Date('2026-04-13'), y: 22 }
]
}
]
};
export default function TrafficLine() {
return <Line options={options} data={data} />;
}
If you see “This method is not implemented: Check that a complete date adapter is provided,” it usually means the adapter wasn’t imported, or there’s a CJS/ESM mismatch. Ensure you import ‘chartjs-adapter-date-fns’ once in your bundle before rendering any time charts. (stackoverflow.com )
Adding zoom and pan
For exploratory analysis, enable zooming and panning with chartjs-plugin-zoom:
npm install chartjs-plugin-zoom
import zoomPlugin from 'chartjs-plugin-zoom';
ChartJS.register(zoomPlugin);
const options: ChartOptions<'line'> = {
// ... your existing options
plugins: {
// ... other plugins
zoom: {
zoom: {
wheel: { enabled: true },
pinch: { enabled: true },
mode: 'x'
},
pan: { enabled: true, mode: 'x' }
}
}
};
The plugin must be registered with Chart.register(zoomPlugin). Then configure the zoom and pan options under plugins.zoom. (chartjs.org )
Making charts beautiful: gradients and theming
In react-chartjs-2 v4+, you no longer pass a function to the data prop to access the canvas; instead, use a ref to access chart.ctx and build gradients inside an effect:
// src/components/ProfitArea.tsx
import React, { useEffect, useRef, useState } from 'react';
import { Chart as ChartJS, LinearScale, TimeScale, PointElement, LineElement, Filler } from 'chart.js';
import 'chartjs-adapter-date-fns';
import { Line } from 'react-chartjs-2';
ChartJS.register(LinearScale, TimeScale, PointElement, LineElement, Filler);
export default function ProfitArea() {
const ref = useRef<any>(null);
const [bg, setBg] = useState<string | CanvasGradient>('#93C5FD');
useEffect(() => {
const chart = ref.current;
if (!chart) return;
const { ctx, chartArea } = chart;
if (!chartArea) return; // Wait for first layout pass
const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
gradient.addColorStop(0, 'rgba(59,130,246,0.35)');
gradient.addColorStop(1, 'rgba(59,130,246,0.02)');
setBg(gradient);
}, []);
return (
<Line
ref={ref}
data={{
datasets: [{ label: 'Profit', data: [{ x: Date.now(), y: 25 }], fill: true, backgroundColor: bg, borderColor: '#3B82F6' }]
}}
options={{ responsive: true, scales: { x: { type: 'time' } } }}
/>
);
}
This approach aligns with the wrapper’s migration notes and allows dynamic theming. (react-chartjs-2.js.org )
Performance for large datasets
Chart.js includes a decimation plugin that can reduce the number of plotted points using algorithms like LTTB or min‑max. Enable it when plotting thousands of points. Also consider turning off animations and parsing to squeeze the most out of canvas rendering. (chartjs.org )
const options: ChartOptions<'line'> = {
animation: false,
parsing: false,
interaction: { mode: 'nearest', axis: 'x', intersect: false },
plugins: {
decimation: { enabled: true, algorithm: 'lttb', samples: 250 }
}
};
Updating data in real time
Because react-chartjs-2 components are just React components, updating state updates the chart. For high‑frequency streams, prefer immutably appending to a fixed‑length array (and decimate older points) to avoid large re-renders. Memoize options with useMemo when they don’t change between updates.
const [points, setPoints] = useState<{x:number;y:number}[]>([]);
useEffect(() => {
const id = setInterval(() => {
setPoints((prev) => [...prev.slice(-999), { x: Date.now(), y: Math.random() * 100 }]);
}, 1000);
return () => clearInterval(id);
}, []);
TypeScript tips
- Use ChartData<‘line’>, ChartOptions<‘bar’>, etc., from chart.js to keep dataset shapes correct at compile time.
- If you interact with the chart instance, annotate refs with React.RefObject
to get IntelliSense for APIs like update or resetZoom (from the zoom plugin).
Working in Next.js or SSR environments
Chart.js renders to a canvas and expects window. If you’re using Next.js, render charts only on the client to avoid SSR errors:
// app/components/ClientLine.tsx (Next.js)
'use client';
import { Line } from 'react-chartjs-2';
// ...imports and registration
export default function ClientLine() { /* render as usual */ }
Alternatively, dynamically import a chart component with ssr: false.
Common pitfalls and fixes
-
“category is not a registered scale”
- Cause: You didn’t register CategoryScale (or you’re relying on tree‑shaking without the right imports).
- Fix: Register CategoryScale explicitly, or during prototyping use import ‘chart.js/auto’ which registers everything for you. (react-chartjs-2.js.org )
-
“This method is not implemented: Check that a complete date adapter is provided.”
- Cause: Time scale in use without a loaded adapter, or a bundling mismatch.
- Fix: Import ‘chartjs-adapter-date-fns’ (or your preferred adapter) once. Ensure consistent module format in your build. (stackoverflow.com )
-
ESM/CommonJS gotchas
- Chart.js v4 and react-chartjs-2 v5 originally shipped as ESM‑only; later, CommonJS support was restored in react-chartjs-2 v5.1. If you see bundling errors in older setups, check your package versions and module type. (react-chartjs-2.js.org )
Putting it together: small dashboard snippet
// src/Dashboard.tsx
import React from 'react';
import SalesBar from './components/SalesBar';
import TrafficLine from './components/TrafficLine';
export default function Dashboard() {
return (
<div style={{ display: 'grid', gap: 24 }}>
<section>
<h2>Sales</h2>
<SalesBar />
</section>
<section>
<h2>Website Traffic</h2>
<TrafficLine />
</section>
</div>
);
}
Now you’ve got categorical comparisons, a time series with tooltips, and the groundwork for interactivity and performance.
Checklist for production
- Register only what you use; avoid ‘chart.js/auto’ in final builds. (react-chartjs-2.js.org )
- Add a date adapter for time axes. (chartjs.org )
- Enable decimation for big lines; disable unnecessary animations. (chartjs.org )
- Register and configure chartjs-plugin-zoom if you need exploration. (chartjs.org )
- Memoize options and cap live buffers for smooth updates.
Wrap‑up
Chart.js delivers a powerful foundation, and react-chartjs-2 makes it feel idiomatic in React. With proper registration, the right adapter for time series, optional zoom/pan, and a few performance toggles, you can ship charts that look sharp and stay fast—even with large datasets. The links above cover the exact APIs and options you’ll use most often as you continue building. (react-chartjs-2.js.org )
Related Posts
React Custom Hooks: A Practical Best Practices Collection
A practical collection of best practices for building, testing, and shipping robust React custom hooks with patterns and code examples.
React Parallel Data Fetching Patterns: From Promise.all to Suspense and Server Components
Master React parallel data fetching with Promise.all, Suspense, React Query, SWR, Next.js, and Router loaders. Avoid waterfalls and ship faster UIs.
Flutter Charts: A Practical Data Visualization Guide
A practical, end-to-end Flutter charts guide: choose libraries, build beautiful charts, optimize performance, add interactivity, accessibility, and tests.