{"id":30055,"date":"2026-04-24T06:19:21","date_gmt":"2026-04-24T06:19:21","guid":{"rendered":"urn:uuid:d85f3d11-4405-4b91-aaf3-14811eb97c6e"},"modified":"2026-04-24T06:19:22","modified_gmt":"2026-04-24T06:19:22","slug":"fetching-real-data-for-highcharts-in-react-using-useeffect","status":"publish","type":"post","link":"https:\/\/www.highcharts.com\/blog\/tutorials\/fetching-real-data-for-highcharts-in-react-using-useeffect\/","title":{"rendered":"Fetching Real Data for Highcharts in React using useEffect"},"content":{"rendered":"\n<p>In the previous post (<a href=\"\" target=\"_blank\">Dynamic Charts in React using useState with @highcharts\/react<\/a>), I used <code>useState<\/code> to update a chart with hardcoded data. In this post, I go one step further, fetching real data from an API and rendering it in a chart using <code>useEffect<\/code>.<\/p>\n<p>This is the pattern behind every real dashboard: data arrives after the component loads, the chart waits, then renders.\n<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What you will build<\/strong><\/h2>\n\n\n\n<p>A line chart that fetches USD to EUR exchange rate historical data from a remote API and renders it automatically when the component mounts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Prerequisites<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Node.js installed (node -v to check)<\/li>\n\n\n\n<li>Basic knowledge of React<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Setting up the project<\/strong><\/h2>\n\n\n\n<p>Create a new React project using Vite:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm create vite@latest . -- --template react\n\n\n<\/code><\/pre>\n\n\n\n<p>Install Highcharts:<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install @highcharts\/react\n<\/code><\/pre>\n\n\n\n<p>Start the dev server:<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm run dev\n<\/code><\/pre>\n\n\n\n<p>Open http:\/\/localhost:5173 in your browser.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Your first chart with real data<\/strong><\/h2>\n\n\n\n<p>Open src\/App.jsx, delete everything and replace it with:<\/p>\n\n\n\n<p><\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" tabindex=\"0\" role=\"region\" aria-label=\"Code block\">\n<pre class=\"wp-block-code\"><code>import { useState, useEffect } from 'react'\nimport { Chart } from '@highcharts\/react'\nimport { XAxis } from '@highcharts\/react'\nimport { LineSeries } from '@highcharts\/react\/series\/Line'\n\nfunction App() {\n  const &#91;data, setData] = useState(&#91;])\n\n  useEffect(() => {\n    fetch('https:\/\/cdn.jsdelivr.net\/gh\/highcharts\/highcharts@latest\/samples\/data\/usdeur.json')\n      .then(response => response.json())\n      .then(data => setData(data))\n  }, &#91;])\n\n  return (\n    &lt;Chart>\n      &lt;XAxis type=\"datetime\" \/>\n      &lt;LineSeries data={data} \/>\n    &lt;\/Chart>\n  )\n}\nexport default App<\/code><\/pre>\n\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" fill=\"none\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 8V5.2c0-1.12 0-1.68.218-2.108a2 2 0 0 1 .874-.874C9.52 2 10.08 2 11.2 2h7.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C22 3.52 22 4.08 22 5.2v7.6c0 1.12 0 1.68-.218 2.108a2 2 0 0 1-.874.874C20.48 16 19.92 16 18.8 16H16M5.2 22h7.6c1.12 0 1.68 0 2.108-.218a2 2 0 0 0 .874-.874C16 20.48 16 19.92 16 18.8v-7.6c0-1.12 0-1.68-.218-2.108a2 2 0 0 0-.874-.874C14.48 8 13.92 8 12.8 8H5.2c-1.12 0-1.68 0-2.108.218a2 2 0 0 0-.874.874C2 9.52 2 10.08 2 11.2v7.6c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874C3.52 22 4.08 22 5.2 22\"><\/path><\/svg>Copy<\/button><\/div>\n<\/div><\/div>\n\n\n\n<p>Save the file. You should see a line chart showing USD to EUR exchange rate data.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Adding a loading state<\/strong><\/h2>\n\n\n\n<p>The chart renders an empty state while the data is being fetched. On a fast connection that gap is barely noticeable, but on a slow network or with a large dataset it can leave the user looking at a blank screen.<\/p>\n\n\n\n<p>The fix is a loading state, a simple boolean that controls what the component renders while it waits for the data.Update App.jsx:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><\/code><\/pre>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" tabindex=\"0\" role=\"region\" aria-label=\"Code block\">\n<pre class=\"wp-block-code\"><code>import { useState, useEffect } from 'react'\nimport { Chart } from '@highcharts\/react'\nimport { XAxis } from '@highcharts\/react'\nimport { LineSeries } from '@highcharts\/react\/series\/Line'\n\nfunction App() {\n  const &#91;data, setData] = useState(&#91;])\n  const &#91;loading, setLoading] = useState(true)\n\n  useEffect(() => {\n    fetch('https:\/\/cdn.jsdelivr.net\/gh\/highcharts\/highcharts@latest\/samples\/data\/usdeur.json')\n      .then(response => response.json())\n      .then(data => {\n        setData(data)\n        setLoading(false)\n      })\n  }, &#91;])\n\n  if (loading) return &lt;div>Loading chart data...&lt;\/div>\n\n  return (\n    &lt;Chart>\n      &lt;XAxis type=\"datetime\" \/>\n      &lt;LineSeries data={data} \/>\n    &lt;\/Chart>\n  )\n}\nexport default App<\/code><\/pre>\n\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" fill=\"none\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 8V5.2c0-1.12 0-1.68.218-2.108a2 2 0 0 1 .874-.874C9.52 2 10.08 2 11.2 2h7.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C22 3.52 22 4.08 22 5.2v7.6c0 1.12 0 1.68-.218 2.108a2 2 0 0 1-.874.874C20.48 16 19.92 16 18.8 16H16M5.2 22h7.6c1.12 0 1.68 0 2.108-.218a2 2 0 0 0 .874-.874C16 20.48 16 19.92 16 18.8v-7.6c0-1.12 0-1.68-.218-2.108a2 2 0 0 0-.874-.874C14.48 8 13.92 8 12.8 8H5.2c-1.12 0-1.68 0-2.108.218a2 2 0 0 0-.874.874C2 9.52 2 10.08 2 11.2v7.6c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874C3.52 22 4.08 22 5.2 22\"><\/path><\/svg>Copy<\/button><\/div>\n<\/div><\/div>\n\n\n\n<p>The component now shows &#8220;Loading chart data&#8230;&#8221; until the fetch completes, then renders the chart.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What just happened<\/strong><\/h2>\n\n\n\n<p>Three things worth understanding:<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>1. useEffectruns after the component mounts<\/strong><\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" tabindex=\"0\" role=\"region\" aria-label=\"Code block\">\n<pre class=\"wp-block-code\"><code>useEffect(() => {\n  fetch(...)\n}, &#91;])<\/code><\/pre>\n\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" fill=\"none\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 8V5.2c0-1.12 0-1.68.218-2.108a2 2 0 0 1 .874-.874C9.52 2 10.08 2 11.2 2h7.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C22 3.52 22 4.08 22 5.2v7.6c0 1.12 0 1.68-.218 2.108a2 2 0 0 1-.874.874C20.48 16 19.92 16 18.8 16H16M5.2 22h7.6c1.12 0 1.68 0 2.108-.218a2 2 0 0 0 .874-.874C16 20.48 16 19.92 16 18.8v-7.6c0-1.12 0-1.68-.218-2.108a2 2 0 0 0-.874-.874C14.48 8 13.92 8 12.8 8H5.2c-1.12 0-1.68 0-2.108.218a2 2 0 0 0-.874.874C2 9.52 2 10.08 2 11.2v7.6c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874C3.52 22 4.08 22 5.2 22\"><\/path><\/svg>Copy<\/button><\/div>\n<\/div><\/div>\n\n\n\n<p>The empty array [] at the end means this effect runs once, when the component first appears on the screen. This is the right place to fetch data.<\/p>\n\n\n\n<p><strong>2. useStatemanages two things<\/strong><\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" tabindex=\"0\" role=\"region\" aria-label=\"Code block\">\n<pre class=\"wp-block-code\"><code>const &#91;data, setData] = useState(&#91;])\nconst &#91;loading, setLoading] = useState(true)<\/code><\/pre>\n\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" fill=\"none\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 8V5.2c0-1.12 0-1.68.218-2.108a2 2 0 0 1 .874-.874C9.52 2 10.08 2 11.2 2h7.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C22 3.52 22 4.08 22 5.2v7.6c0 1.12 0 1.68-.218 2.108a2 2 0 0 1-.874.874C20.48 16 19.92 16 18.8 16H16M5.2 22h7.6c1.12 0 1.68 0 2.108-.218a2 2 0 0 0 .874-.874C16 20.48 16 19.92 16 18.8v-7.6c0-1.12 0-1.68-.218-2.108a2 2 0 0 0-.874-.874C14.48 8 13.92 8 12.8 8H5.2c-1.12 0-1.68 0-2.108.218a2 2 0 0 0-.874.874C2 9.52 2 10.08 2 11.2v7.6c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874C3.52 22 4.08 22 5.2 22\"><\/path><\/svg>Copy<\/button><\/div>\n<\/div><\/div>\n\n\n\n<p>data holds the chart values. loading controls what the user sees while waiting. Both update when the fetch completes.<\/p>\n\n\n\n<p><strong>3. The chart only renders when data is ready<\/strong><\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" tabindex=\"0\" role=\"region\" aria-label=\"Code block\">\n<pre class=\"wp-block-code\"><code>if (loading) return &lt;div>Loading chart data...&lt;\/div><\/code><\/pre>\n\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" fill=\"none\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 8V5.2c0-1.12 0-1.68.218-2.108a2 2 0 0 1 .874-.874C9.52 2 10.08 2 11.2 2h7.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C22 3.52 22 4.08 22 5.2v7.6c0 1.12 0 1.68-.218 2.108a2 2 0 0 1-.874.874C20.48 16 19.92 16 18.8 16H16M5.2 22h7.6c1.12 0 1.68 0 2.108-.218a2 2 0 0 0 .874-.874C16 20.48 16 19.92 16 18.8v-7.6c0-1.12 0-1.68-.218-2.108a2 2 0 0 0-.874-.874C14.48 8 13.92 8 12.8 8H5.2c-1.12 0-1.68 0-2.108.218a2 2 0 0 0-.874.874C2 9.52 2 10.08 2 11.2v7.6c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874C3.52 22 4.08 22 5.2 22\"><\/path><\/svg>Copy<\/button><\/div>\n<\/div><\/div>\n\n\n\n<p>This line prevents the chart from rendering with an empty dataset. The user sees a loading message, then the chart appears, clean and predictable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Why this matters<\/strong><\/h2>\n\n\n\n<p>Most real chart in production follows this pattern. The data does not exist at render time, it arrives from an API, a database, or a WebSocket. useEffect is how you bridge that gap in React.<\/p>\n\n\n\n<p>The loading state is not optional. Without it, your chart renders empty, then jumps to the data, a jarring experience that looks broken even when it is working correctly.In the next post, I will look at rendering large datasets efficiently using the Boost module and useMemo.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous post (Dynamic Charts in React using useState with @highcharts\/react), I used useState to update a chart with hardcoded data. In this post, I go one step further, fetching real data from an API and rendering it in a chart using useEffect. This is the pattern behind every real dashboard: data arrives after [&hellip;]<\/p>\n","protected":false},"author":32,"featured_media":30221,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"meta_title":"Fetching Real Data for Highcharts in React using useEffect | @highcharts\/react","meta_description":"Learn how to fetch live API data and render it in a Highcharts line chart using useEffect and useState in React. Includes loading state handling","hc_selected_options":[],"footnotes":""},"categories":[224,210],"tags":[824],"coauthors":[699],"class_list":["post-30055","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-post","category-tutorials","tag-react"],"_links":{"self":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30055","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/users\/32"}],"replies":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/comments?post=30055"}],"version-history":[{"count":3,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30055\/revisions"}],"predecessor-version":[{"id":30228,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30055\/revisions\/30228"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media\/30221"}],"wp:attachment":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media?parent=30055"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/categories?post=30055"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/tags?post=30055"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/coauthors?post=30055"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}