1

I'm using chartjs/react-chartjs-s to draw some plots in React/NextJS. I want to use an image for the pointStyle for one plot in specific. In plain JavaScript, an image can be created using const i = new Image() and used as a pointStyle in ChartJS.

In React I get an error ReferenceError: Image is not defined. If I try to import Image from next/image and use that, then the image does not appear on initial render (like here which interestingly can use new Image()), and if I click on a datum on the chart I get an error Error: Rendered more hooks than during the previous render.

TLDR: Does anyone know how to use an image/icon as a pointStyle using Next.js, ChartJS, and react-chartjs-2? Thanks!

I'm using:

  1. react v. 18.2.0
  2. react-dom v. 18.2.0
  3. next v 13.0.05
  4. chart.js v. 4.1.1
  5. react-chartjs-2 v. 5.1.0
WhoDatBoy
  • 329
  • 4
  • 15
  • Please have a look at this demo https://codesandbox.io/s/radar-chart-react-shyv49?file=/src/App.js – DreamBold Jan 04 '23 at 23:18
  • @DreamBold thanks for the help! I moved the `new Image()` statement into my component return, but now I am getting an error that says `TypeError: Cannot add property src, obejct is not extensible` – WhoDatBoy Jan 04 '23 at 23:44
  • Can you replicate your code in the codesandbox? – DreamBold Jan 04 '23 at 23:46
  • https://codesandbox.io/s/unruffled-wave-qhgzv0?file=/src/App.tsx Here's the typescript template – DreamBold Jan 04 '23 at 23:49
  • @DreamBold I created a component from your code which was working fine. After making some edits, I again got the `Image not defined` error. I `Cntl + Z`'ed until I got back to your original code (and saved), and the error persisted. I am completely at a loss for what could be causing this. – WhoDatBoy Jan 05 '23 at 00:06
  • @DreamBold it appears that whenever I refresh the browser I get the `ReferenceError: Image not defined` error – WhoDatBoy Jan 05 '23 at 00:14
  • https://nextjs.org/docs/api-reference/next/image#:~:text=Here%20is%20an%20example%20of%20using%20a%20custom%20loader%3A Here's how to import an image in next.js. Next.js is SSR(server-side rendering), and that's why you see the error – DreamBold Jan 05 '23 at 00:23
  • 1
    @DreamBold - thanks for that. The only problem now is that ChartJS expects an inbuilt JS object and not a Next object. Either way, thanks for your help! It's sincerely appreciated. I'll close out this question because the true answer is not really related to my initial question. Thanks! – WhoDatBoy Jan 05 '23 at 00:49
  • 1
    You'd better add the solution to this question when you fix it so it can help the others. Good luck! – DreamBold Jan 05 '23 at 06:08

1 Answers1

1

Solution:

new Image() reference the DOM, but since Next uses SSR, you have to utilize the useRef, useState, and useEffect hooks to circumvent the issue of trying to access the DOM.

import React, { useEffect, useRef, useState } from "react";
import {
  Chart as ChartJS,
  RadialLinearScale,
  PointElement,
  LineElement,
  Filler,
  Tooltip,
  Legend
} from "chart.js";
import { Radar } from "../node_modules/react-chartjs-2/dist";

ChartJS.register(
  RadialLinearScale,
  PointElement,
  LineElement,
  Filler,
  Tooltip,
  Legend
);

function App() {
  // useRef lets you reference a DOM object, persistent between renders
  const ref = useRef();
  const [onClient, setOnClient] = useState();
  
  // On first render, set onClient flag to true
  useEffect(() => {
    setOnClient(true);
  }, []);

  // On first render, set the current value of the ref to the image
  useEffect(() => {
    ref.current = new Image(25, 25);
    ref.current.src = "https://i.stack.imgur.com/gXQrT.png";
  }, []);

  const data = {
    labels: ["Business 1", "Business 2", "Business 3"],
    datasets: [
      {
        label: "Number of Businesses",
        data: [1300, 400, 160],
        backgroundColor: "rgba(255, 99, 132, 0.2)",
        borderColor: "rgba(255, 99, 132, 1)",
        borderWidth: 1,
        // Set the value of pointStyle to the ref value
        pointStyle: ref.current
      }
    ]
  };

  return (
    <div className="App">
      // Return the graph if rendering is happening on the client
      {onClient && <Radar data={data} />}

      <form>
        <input type="text" placeholder="Label" />
        <input type="text" placeholder="Dataset" />
        <button>Add Data</button>
      </form>
    </div>
  );
}

export default App;
WhoDatBoy
  • 329
  • 4
  • 15