{"id":30229,"date":"2026-04-28T07:19:49","date_gmt":"2026-04-28T07:19:49","guid":{"rendered":"urn:uuid:a2342279-74cd-4029-af4e-9c5901e364d6"},"modified":"2026-04-28T09:24:15","modified_gmt":"2026-04-28T09:24:15","slug":"rendering-50000-data-points-in-react-with-usememo-and-highcharts-react","status":"publish","type":"post","link":"https:\/\/www.highcharts.com\/blog\/post\/rendering-50000-data-points-in-react-with-usememo-and-highcharts-react\/","title":{"rendered":"Rendering 50,000 Data Points in React with useMemo and @highcharts\/react"},"content":{"rendered":"\n<p>In the previous post, I fetched real data from an API using useEffect. In this post, I tackle a different challenge, rendering large datasets without freezing the browser.<\/p>\n\n\n\n<p>This is the pattern behind financial dashboards, sensor data feeds, and any application that works with tens of thousands of data points.<\/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 renders 50,000 data points efficiently using useMemo to ensure the dataset is only generated once, not on every render.<\/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\n\n\n<li>Read Post 1 Dynamic Charts in React using useState with @highcharts\/react<\/li>\n\n\n\n<li>Read Post 2 Fetching Real Data for Highcharts in React using useEffect<\/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<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>npm create vite@latest . -- --template react<\/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>Install Highcharts:<\/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>npm install @highcharts\/react<\/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>Start the dev server:<\/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>npm run dev<\/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>Open <code>http:\/\/localhost:5173<\/code> in your browser.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Generating a large dataset<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>Open <code>src\/App.jsx<\/code>, delete everything and replace it with:<\/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, useMemo } from 'react'\nimport { Chart } from '@highcharts\/react'\nimport { LineSeries } from '@highcharts\/react\/series\/Line'\nfunction App() {\n&nbsp;&nbsp;const &#91;points] = useState(50000)\n&nbsp;&nbsp;const data = useMemo(() =&gt; {\n&nbsp;&nbsp;&nbsp;&nbsp;let value = 0\n&nbsp;&nbsp;&nbsp;&nbsp;return Array.from({ length: points }, () =&gt; {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value += Math.round((Math.random() - 0.5) * 10)\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return value\n&nbsp;&nbsp;&nbsp;&nbsp;})\n&nbsp;&nbsp;}, &#91;points])\n&nbsp;&nbsp;return (\n&nbsp;&nbsp;&nbsp;&nbsp;&lt;Chart&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;LineSeries data={data} \/&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&lt;\/Chart&gt;\n&nbsp;&nbsp;)\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><\/p>\n\n\n\n<p>Save the file. You should see a line chart rendering 50,000 data points smoothly in the browser.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What just happened<\/strong><\/h2>\n\n\n\n<p>Two things worth understanding:<\/p>\n\n\n\n<p><strong>1. <\/strong><strong>useMemo<\/strong><strong> computes the data once<\/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 data = useMemo(() =&gt; {\n  let value = 0\n  return Array.from({ length: points }, () =&gt; {\n    value += Math.round((Math.random() - 0.5) * 10)\n    return value\n  })\n}, &#91;points])<\/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>useMemo takes two arguments, a function that returns a value, and a dependency array. It runs the function once and caches the result. It only recalculates when something in the dependency array changes, in this case, when points changes.Without useMemo, the 50,000 point dataset would be regenerated on every render. That means every state change, every prop update, every re-render, recalculating 50,000 values each time. With useMemo, it happens once.<\/p>\n\n\n\n<p><strong>2. The chart receives a stable reference<\/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>&lt;LineSeries data={data} \/&gt;<\/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>Because useMemo returns the same array reference between renders, @highcharts\/react knows the data has not changed and skips unnecessary updates. This is what keeps the chart fast.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>When to use useMemo<\/strong><\/h2>\n\n\n\n<p>useMemo is not always needed. For small datasets, a few dozen points, the calculation is fast enough that caching makes no difference. Use it when:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Generating or transforming large datasets (thousands of points)<\/li>\n\n\n\n<li>Running expensive calculations that feed into a chart<\/li>\n\n\n\n<li>Passing data through multiple components and you want a stable reference<\/li>\n<\/ul>\n\n\n\n<p>The rule of thumb: if generating the data takes noticeable time, wrap it in useMemo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Why this matters<\/strong><\/h2>\n\n\n\n<p>Charts that work beautifully with 100 data points often struggle with 10,000. useMemo is not a chart optimization, it is a React optimisation that prevents your app from doing unnecessary work before the chart even gets involved.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous post, I fetched real data from an API using useEffect. In this post, I tackle a different challenge, rendering large datasets without freezing the browser. This is the pattern behind financial dashboards, sensor data feeds, and any application that works with tens of thousands of data points. What you will build A [&hellip;]<\/p>\n","protected":false},"author":32,"featured_media":30231,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"meta_title":"Rendering 50,000 Data Points in React with useMemo and @highcharts\/react Mustapha Mekhatria Avatar by Mustapha Mekhatria 3 minutes read In the previous post, I fetched real data from an API using useEffect. In this post, I tackle a different challenge, rendering large datasets without freezing the browser. This is the pattern behind financial dashboards, sensor data feeds, and any application that works with tens of thousands of data points. What you will build A line chart that renders 50,000 data points efficiently using useMemo to ensure the dataset is only generated once, not on every render. Prerequisites Node.js installed (node -v to check) Basic knowledge of React Read Post 1 Dynamic Charts in React using useState with @highcharts\/react Read Post 2 Fetching Real Data for Highcharts in React using useEffect Setting up the project Create a new React project using Vite: npm create vite@latest . -- --template react Copy Install Highcharts: npm install @highcharts\/react Copy Start the dev server: npm run dev Copy Open http:\/\/localhost:5173 in your browser. Generating a large dataset Open src\/App.jsx, delete everything and replace it with: import { useState, useMemo } from 'react' import { Chart } from '@highcharts\/react' import { LineSeries } from '@highcharts\/react\/series\/Line' function App() { const [points] = useState(50000) const data = useMemo(() => { let value = 0 return Array.from({ length: points }, () => { value += Math.round((Math.random() - 0.5) * 10) return value }) }, [points]) return ( ) } export default App Copy Save the file. You should see a line chart rendering 50,000 data points smoothly in the browser. What just happened Two things worth understanding: 1. useMemo computes the data once const data = useMemo(() => { let value = 0 return Array.from({ length: points }, () => { value += Math.round((Math.random() - 0.5) * 10) return value }) }, [points]) Copy useMemo takes two arguments, a function that returns a value, and a dependency array. It runs the function once and caches the result. It only recalculates when something in the dependency array changes, in this case, when points changes.Without useMemo, the 50,000 point dataset would be regenerated on every render. That means every state change, every prop update, every re-render, recalculating 50,000 values each time. With useMemo, it happens once. Without useMemo, the 50,000 point dataset would be regenerated on every render. That means every state change, every prop update, every re-render, recalculating 50,000 values each time. With useMemo, it happens once 2. The chart receives a stable reference Copy Because useMemo returns the same array reference between renders, @highcharts\/react knows the data has not changed and skips unnecessary updates. This is what keeps the chart fast. When to use useMemo useMemo is not always needed. For small datasets, a few dozen points, the calculation is fast enough that caching makes no difference. Use it when: Generating or transforming large datasets (thousands of points) Running expensive calculations that feed into a chart Passing data through multiple components and you want a stable reference The rule of thumb: if generating the data takes noticeable time, wrap it in useMemo. Why this matters Charts that work beautifully with 100 data points often struggle with 10,000. useMemo is not a chart optimization, it is a React optimisation that prevents your app from doing unnecessary work before the chart even gets involved.","meta_description":"Learn how to render 50,000 data points in React without freezing the browser using useMemo and @highcharts\/react. A practical guide for high-performance charts.","hc_selected_options":[],"footnotes":""},"categories":[224],"tags":[824],"coauthors":[699],"class_list":["post-30229","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-post","tag-react"],"_links":{"self":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30229","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=30229"}],"version-history":[{"count":3,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30229\/revisions"}],"predecessor-version":[{"id":30256,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30229\/revisions\/30256"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media\/30231"}],"wp:attachment":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media?parent=30229"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/categories?post=30229"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/tags?post=30229"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/coauthors?post=30229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}