ScreenshotNeo

BlogHow-to

How to Create a Graph in React.js

Build responsive React graphs with Recharts or Chart.js, handle real data and errors, and capture graph pages automatically with ScreenshotNeo.

By the ScreenshotNeo team1 October 20268 min read

Direct answer: In a React application, create a graph by installing a chart library and rendering its chart component with an array of data objects. Recharts is the simplest starting point for a component-based React graph. Chart.js with react-chartjs-2 is a strong alternative when you already use the Chart.js ecosystem.

1. Choose a React graph library

Need Start with Why
Declarative React components Recharts Its project describes a composable charting library built on React components and SVG rendering. Read the Recharts project documentation.
Chart.js configuration and plugins Chart.js with react-chartjs-2 The wrapper provides React components around Chart.js. Read the wrapper quickstart.
Selective Chart.js bundle Chart.js with explicit registration You register only the controllers, elements, scales and plugins your chart needs. Read the integration guide.

Choose based on chart types, interactions, accessibility requirements, rendering format, bundle constraints and maintenance needs. No library is universally fastest or best for every workload.

2. Build a line graph with Recharts

Install Recharts

npm install recharts

The Recharts guide recommends npm for starting a single-page application. Its displayed project version was 3.10.1 when the source was checked, but package versions change, so use the version installed by your package manager. Recharts is MIT licensed. Installation guide.

Create the graph component

import {
  CartesianGrid,
  Line,
  LineChart,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from 'recharts';

const data = [
  { month: 'Jan', visits: 120 },
  { month: 'Feb', visits: 180 },
  { month: 'Mar', visits: 150 },
  { month: 'Apr', visits: 230 },
];

export function VisitsGraph() {
  return (
    <section aria-labelledby="visits-title">
      <h2 id="visits-title">Monthly visits</h2>
      <div style={{ width: '100%', height: 300 }}>
        <ResponsiveContainer>
          <LineChart data={data} margin={{ top: 12, right: 20, left: 8, bottom: 12 }}>
            <CartesianGrid strokeDasharray="3 3" />
            <XAxis dataKey="month" />
            <YAxis />
            <Tooltip />
            <Line type="monotone" dataKey="visits" stroke="#2563eb" strokeWidth={2} />
          </LineChart>
        </div>
        <table>
          <caption>Monthly visits data</caption>
          <thead><tr><th>Month</th><th>Visits</th></tr></thead>
          <tbody>
            {data.map((row) => (
              <tr key={row.month}><td>{row.month}</td><td>{row.visits}</td></tr>
            ))}
          </tbody>
        </table>
      </section>
    </section>
  );
}

data is an array of records. XAxis dataKey="month" selects the category field, while Line dataKey="visits" selects the numeric series. The axes provide context, the grid helps readers estimate values and the tooltip exposes an exact point value. ResponsiveContainer fills its parent, so the parent must have a defined height.

Render the component

import { VisitsGraph } from './VisitsGraph';

export default function Dashboard() {
  return (
    <main>
      <VisitsGraph />
    </main>
  );
}

3. Add multiple series, labels and formatting

const data = [
  { month: 'Jan', organic: 120, paid: 80 },
  { month: 'Feb', organic: 180, paid: 110 },
  { month: 'Mar', organic: 150, paid: 140 },
];

<LineChart data={data}>
  <CartesianGrid strokeDasharray="3 3" />
  <XAxis dataKey="month" />
  <YAxis tickFormatter={(value) => `${value}`} />
  <Tooltip />
  <Legend />
  <Line name="Organic" dataKey="organic" stroke="#2563eb" />
  <Line name="Paid" dataKey="paid" stroke="#16a34a" />
</LineChart>

Use a stable key for each row, keep numeric values as numbers, and format displayed values separately from the raw data. Use a bar chart for category comparison, an area chart for cumulative movement and a pie chart only when a small number of parts must be compared.

4. Load graph data from an API

import { useEffect, useState } from 'react';
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';

export function ApiGraph() {
  const [data, setData] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    fetch('/api/visits', { signal: controller.signal })
      .then((response) => {
        if (!response.ok) throw new Error(`Request failed: ${response.status}`);
        return response.json();
      })
      .then((rows) => setData(rows.map((row) => ({
        month: row.month,
        visits: Number(row.visits),
      }))))
      .catch((err) => {
        if (err.name !== 'AbortError') setError(err.message);
      });
    return () => controller.abort();
  }, []);

  if (error) return <p role="alert">Could not load graph: {error}</p>;
  if (!data.length) return <p>Loading graph…</p>;

  return (
    <div style={{ width: '100%', height: 300 }}>
      <ResponsiveContainer>
        <LineChart data={data}>
          <XAxis dataKey="month" />
          <YAxis />
          <Tooltip />
          <Line dataKey="visits" stroke="#2563eb" />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
}

Validate the response shape before rendering. Decide how to represent missing, zero and invalid values; do not silently convert malformed values to misleading numbers.

5. Chart.js with react-chartjs-2

Install both packages

npm install chart.js react-chartjs-2

Register the pieces you use

import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  LineElement,
  PointElement,
  Tooltip,
} from 'chart.js';
import { Line } from 'react-chartjs-2';

ChartJS.register(CategoryScale, LinearScale, LineElement, PointElement, Tooltip);

const data = {
  labels: ['Jan', 'Feb', 'Mar'],
  datasets: [{
    label: 'Visits',
    data: [120, 180, 150],
    borderColor: '#2563eb',
  }],
};

export function VisitsGraph() {
  return <Line data={data} />;
}

Chart.js is tree-shakeable. Register the controllers, elements, scales and plugins required by the selected chart. For a simpler setup, the Chart.js documentation describes import 'chart.js/auto'; that shortcut can include more functionality than selective imports. The wrapper quickstart recommends Chart.js v4 and documents support for v3, so check peer dependencies and the current migration guide when upgrading.

6. Important graph options

  • Responsive sizing: give the chart container an explicit height; use CSS media queries for small screens.
  • Full data sets: aggregate very dense time series before rendering, or provide zoom and filtering controls.
  • Dark mode: switch stroke, grid, tick and tooltip colors when the application theme changes.
  • Interaction: add hover tooltips, legends, selection and date-range controls only when they answer a user task.
  • Accessibility: provide a meaningful heading, describe the trend in surrounding text and include a table or downloadable data when exact values matter. Do not rely on color alone.
  • Internationalization: format dates and numbers with Intl.DateTimeFormat and Intl.NumberFormat rather than hard-coded separators.
  • Server rendering: keep browser-only chart code in a client component in frameworks that distinguish server and client components. Ensure the first client render matches server output.

7. Common errors and fixes

Error Cause Fix
Chart has zero width or height The responsive component’s parent has no resolved dimensions. Set a real height on the parent and ensure it is visible when the chart mounts.
Blank Chart.js canvas A required scale, element, controller or plugin was not registered. Register every piece used by the chart, or temporarily use chart.js/auto.
Tooltips show incorrect values Values are strings, units are mixed or the wrong dataKey is selected. Normalize API data and verify each field’s type and name.
Graph flickers on updates New object and array references are created on every render. Memoize expensive transformations and keep stable component keys.
Dates sort incorrectly Display strings are being compared lexicographically. Sort by timestamps, then format dates for display.
Graph is unreadable on mobile Too many labels or series are shown at once. Reduce tick density, allow horizontal space, aggregate points or let users filter series.
Hydration mismatch Browser-only dimensions or time-dependent values differ between server and client. Render the chart on the client after mount and keep initial data deterministic.

8. Performance, reliability and cost

  • Keep the rendered point count appropriate to the viewport. Aggregate or window large data sets rather than drawing every raw event.
  • Memoize derived data and avoid recreating chart configuration objects when inputs have not changed.
  • Lazy-load a chart-heavy route if most users do not open it.
  • Handle loading, empty, error and partial-data states explicitly.
  • Abort obsolete fetches when a component unmounts or a date range changes.
  • Do not claim a universal performance winner between Recharts and Chart.js. Measure your chart types, data volume and target devices.
  • The libraries themselves do not charge per rendered graph; your costs come from application hosting, data APIs and any capture service used to archive or share the result.

9. Or skip the browser setup

If your goal is to save a rendered graph page as an image or PDF, ScreenshotNeo provides a website screenshot API and MCP server. It accepts a URL and returns PNG, JPEG, WebP or PDF. Cookie and consent banners, newsletter popups and chat widgets are removed before capture; bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.

One request is enough:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-site.example/dashboard -o graph.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://your-site.example/dashboard"},
    timeout=90,
)
r.raise_for_status()
open("graph.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://your-site.example/dashboard',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then((fs) => fs.writeFile('graph.webp', image));

See the ScreenshotNeo API documentation for authentication and options. You can select full-page or element capture, wait for a selector or network idle, set a viewport and device preset, apply custom CSS or JavaScript, hide selectors, use cookies and headers, choose dark mode, create PDFs, cache with a chosen TTL, submit async jobs, capture up to 100 URLs in a bulk call and use signed links. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

There is a free plan with 1,000 screenshots per month and no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.

10. FAQ

Should I use SVG or Canvas?

Use the rendering mode selected by your library and workload. Recharts composes SVG-based React components; Chart.js renders to canvas. Evaluate text rendering, interaction, accessibility and data volume for your application.

Can I draw a graph without a library?

Yes, with SVG, Canvas or HTML, but you must implement scales, axes, tooltips, resizing, keyboard behavior and updates yourself. A library is usually faster to maintain for standard charts.

How do I export the graph?

For an image or PDF of the rendered page, use a browser capture service such as ScreenshotNeo. For raw data export, generate CSV or JSON from the same normalized data used by the component.

Why does my graph not update?

Check that state changes produce a new data array, values are valid numbers, the selected field names match and no memoization is holding stale props.

Where should I learn more?

Start with the official Recharts, react-chartjs-2 and Chart.js documentation linked above. For a broader D3 and React curriculum, Apress lists Integrating D3.js with React: Learn to Bring Data Visualization to Life by Elad Elrom (2021, ISBN 978-1-4842-7051-6).