react-apexcharts icon indicating copy to clipboard operation
react-apexcharts copied to clipboard

Using apexcharts with storybook and nextjs

Open Jaypov opened this issue 4 years ago • 5 comments

I have a component in storybook that uses apexcharts. My storybook component library should be able to be used by normal react and also SSR react.

dynamic imports do not work with my storybook library. how can i solve the issue of 'window is not defined' when using nextjs.

I have tried excluding apex charts from my bundle inside my rollup file and importing using next/script but this will lead to module not found.

any suggestions?

Jaypov avatar Nov 04 '21 18:11 Jaypov

Assuming, the error only happens when rendering, you can try the following:

import Chart from "react-apexcharts";

const MyComponent = () => {
  const isSSR = typeof window === undefined;
  return !isSSR && <Chart ... />
}

ccssmnn avatar Jan 03 '22 08:01 ccssmnn

I just saw that this library is accessing window when it's imported:

https://github.com/apexcharts/react-apexcharts/blob/be616b8d5922696419f6edaadf2ff9453d6fa615/src/react-apexcharts.jsx#L5

My solution from above does not work here. You need to import react-apexcharts inside a useEffect hook, to only import it on the client side. Like this:

const MyComponent = () => {
  const [Chart, setChart] = useState(null)
  useEffect(() => { // useEffect is triggered after MyComponent is mounted (only on the client side)
    import("react-apexcharts")
    .then((Component) => setChart(Component));
  }, []); // fire this effect only once
  return Chart && <Chart ... />
}

I hope this helps

ccssmnn avatar Jan 03 '22 09:01 ccssmnn

an alternative method is using nextjs dynamic import as mentioned in #240

MeMihir avatar Jan 26 '22 14:01 MeMihir

I just saw that this library is accessing window when it's imported:

https://github.com/apexcharts/react-apexcharts/blob/be616b8d5922696419f6edaadf2ff9453d6fa615/src/react-apexcharts.jsx#L5

My solution from above does not work here. You need to import react-apexcharts inside a useEffect hook, to only import it on the client side. Like this:

const MyComponent = () => {
  const [Chart, setChart] = useState(null)
  useEffect(() => { // useEffect is triggered after MyComponent is mounted (only on the client side)
    import("react-apexcharts")
    .then((Component) => setChart(Component));
  }, []); // fire this effect only once
  return Chart && <Chart ... />
}

I hope this helps

this is not working TypeError: Cannot call a class as a function

ArchanaKumari-Delhivery avatar Feb 28 '24 11:02 ArchanaKumari-Delhivery

For further reference, I had the same problem using it with Remix + Vite, here's how I got it running:

// app/ui/Chart.tsx

import { useEffect, useRef, useState } from 'react'
import type { Props as ReactApexChartsProps } from 'react-apexcharts'

export function Chart(props: ReactApexChartsProps) {
  const ApexChart = useLoadReactApexCharts()

  if (ApexChart == null) {
    return null
  }

  const ChartComponent = ApexChart.default

  return <ChartComponent {...props} />
}

type ReactApexChartsType = React.NamedExoticComponent<ReactApexChartsProps>

type ReactApexChartModule = {
  __esModule: true
  default: ReactApexChartsType
}

/**
 * react-apexcharts does not support SSR.
 *
 * In Remix, there's no easy way to lazy import it (at least not using React 17),
 * so we need to do this workaround which forces only to load `react-apexcharts`
 * in the client.
 *
 */
function useLoadReactApexCharts() {
  const appexRef = useRef<boolean>(false)

  const [apexChartModule, setApexChartModule] =
    useState<ReactApexChartModule | null>(null)

  useEffect(() => {
    async function loadAppex() {
      const module = (await import(
        'react-apexcharts'
      )) as unknown as ReactApexChartModule

      /**
       * For some reason, the type inference here is not working correctly.
       *
       * It assumes that the `default` property is the component itself, but
       * when we console log it, it's another module. My gut feeling is that
       * due the module transpilation, it converts to an object that contains the shape
       * specified in the `ReactApexChartModule` type.
       */
      setApexChartModule(module)
    }

    if (appexRef.current === false) {
      loadAppex()
    }
  }, [appexRef])

  return apexChartModule
}

raulfdm avatar Apr 03 '24 10:04 raulfdm