react-apexcharts
react-apexcharts copied to clipboard
Using apexcharts with storybook and nextjs
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?
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 ... />
}
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
an alternative method is using nextjs dynamic import as mentioned in #240
I just saw that this library is accessing
windowwhen 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-apexchartsinside auseEffecthook, 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
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
}