admin管理员组

文章数量:1279117

I am using lightweight-charts in NextJS 15 and followed their React Advanced Example. But the chart is not rendered. I have tried to diable ssr for the Chart Component. There is no error in console.

Here is the default Next App with just this Chart Component

src/app/page.tsx

"use client";
import dynamic from 'next/dynamic';
const App = dynamic(() => import('@/components/ChartComponent').then(mod => mod.App), { ssr: false });

export default function Home(props) {
    return (
        <App></App>
    );
}

src/components/ChartComponent.tsx

"use client";
import { createChart, LineSeries, AreaSeries } from 'lightweight-charts';
import React, {
    createContext,
    forwardRef,
    useCallback,
    useContext,
    useEffect,
    useImperativeHandle,
    useLayoutEffect,
    useRef,
    useState,
} from 'react';

const Context = createContext();

const initialData = [
    { time: '2018-10-11', value: 52.89 },
    { time: '2018-10-12', value: 51.65 },
    { time: '2018-10-13', value: 51.56 },
    { time: '2018-10-14', value: 50.19 },
    { time: '2018-10-15', value: 51.86 },
    { time: '2018-10-16', value: 51.25 },
];

const initialData2 = [
    { time: '2018-10-11', value: 42.89 },
    { time: '2018-10-12', value: 41.65 },
    { time: '2018-10-13', value: 41.56 },
    { time: '2018-10-14', value: 40.19 },
    { time: '2018-10-15', value: 41.86 },
    { time: '2018-10-16', value: 41.25 },
];
const currentDate = new Date(initialData[initialData.length - 1].time);

export const App = props => {
    const {
        colors: {
            backgroundColor = 'white',
            lineColor = '#2962FF',
            textColor = 'black',
        } = {},
    } = props;

    const [chartLayoutOptions, setChartLayoutOptions] = useState({});
    // The following variables illustrate how a series could be updated.
    const series1 = useRef(null);
    const series2 = useRef(null);
    const [started, setStarted] = useState(false);
    const [isSecondSeriesActive, setIsSecondSeriesActive] = useState(false);

    // The purpose of this effect is purely to show how a series could
    // be updated using the `reference` passed to the `Series` component.
    useEffect(() => {
        if (series1.current === null) {
            return;
        }
        let intervalId;

        if (started) {
            intervalId = setInterval(() => {
                currentDate.setDate(currentDate.getDate() + 1);
                const next = {
                    time: currentDate.toISOString().slice(0, 10),
                    value: 53 - 2 * Math.random(),
                };
                series1.current.update(next);
                if (series2.current) {
                    series2.current.update({
                        ...next,
                        value: 43 - 2 * Math.random(),
                    });
                }
            }, 1000);
        }
        return () => clearInterval(intervalId);
    }, [started]);

    useEffect(() => {
        setChartLayoutOptions({
            background: {
                color: backgroundColor,
            },
            textColor,
        });
    }, [backgroundColor, textColor]);

    return (
        <>
            <button type="button" onClick={() => setStarted(current => !current)}>
                {started ? 'Stop updating' : 'Start updating series'}
            </button>
            <button type="button" onClick={() => setIsSecondSeriesActive(current => !current)}>
                {isSecondSeriesActive ? 'Remove second series' : 'Add second series'}
            </button>
            <Chart layout={chartLayoutOptions}>
                <Series
                    ref={series1}
                    type={'line'}
                    data={initialData}
                    color={lineColor}
                />
                {isSecondSeriesActive && <Series
                    ref={series2}
                    type={'area'}
                    data={initialData2}
                    color={lineColor}
                />}
            </Chart>
        </>
    );
};

export function Chart(props) {
    const [container, setContainer] = useState(false);
    const handleRef = useCallback(ref => setContainer(ref), []);
    return (
        <div ref={handleRef}>
            {container && <ChartContainer {...props} container={container} />}
        </div>
    );
}

export const ChartContainer = forwardRef((props, ref) => {
    const { children, container, layout, ...rest } = props;

    const chartApiRef = useRef({
        isRemoved: false,
        api() {
            if (!this._api) {
                this._api = createChart(container, {
                    ...rest,
                    layout,
                    width: container.clientWidth,
                    height: 300,
                });
                this._api.timeScale().fitContent();
            }
            return this._api;
        },
        free(series) {
            if (this._api && series) {
                this._api.removeSeries(series);
            }
        },
    });

    useLayoutEffect(() => {
        const currentRef = chartApiRef.current;
        const chart = currentRef.api();

        const handleResize = () => {
            chart.applyOptions({
                ...rest,
                width: container.clientWidth,
            });
        };

        window.addEventListener('resize', handleResize);
        return () => {
            window.removeEventListener('resize', handleResize);
            chartApiRef.current.isRemoved = true;
            chart.remove();
        };
    }, []);

    useLayoutEffect(() => {
        const currentRef = chartApiRef.current;
        currentRef.api();
    }, []);

    useLayoutEffect(() => {
        const currentRef = chartApiRef.current;
        currentRef.api().applyOptions(rest);
    }, []);

    useImperativeHandle(ref, () => chartApiRef.current.api(), []);

    useEffect(() => {
        const currentRef = chartApiRef.current;
        currentRef.api().applyOptions({ layout });
    }, [layout]);

    return (
        <Context.Provider value={chartApiRef.current}>
            {props.children}
        </Context.Provider>
    );
});
ChartContainer.displayName = 'ChartContainer';

export const Series = forwardRef((props, ref) => {
    const parent = useContext(Context);
    const context = useRef({
        api() {
            if (!this._api) {
                const { children, data, type, ...rest } = props;
                this._api =
                    type === 'line'
                        ? parent.api().addSeries(LineSeries, rest)
                        : parent.api().addSeries(AreaSeries, rest);
                this._api.setData(data);
            }
            return this._api;
        },
        free() {
            // check if parent component was removed already
            if (this._api && !parent.isRemoved) {
                // remove only current series
                parent.free(this._api);
            }
        },
    });

    useLayoutEffect(() => {
        const currentRef = context.current;
        currentRef.api();

        return () => currentRef.free();
    }, []);

    useLayoutEffect(() => {
        const currentRef = context.current;
        const { children, data, ...rest } = props;
        currentRef.api().applyOptions(rest);
    });

    useImperativeHandle(ref, () => context.current.api(), []);

    return (
        <Context.Provider value={context.current}>
            {props.children}
        </Context.Provider>
    );
});
Series.displayName = 'Series';

Apart from these two changes, there are no changes to any of the files after creating nextjs project. I'm using nextjs:15.1.7 & reactv19 & lightweight-charts:5.0.2

I tried updating to dynamic import and disable ssr, but did not work

I am using lightweight-charts in NextJS 15 and followed their React Advanced Example. But the chart is not rendered. I have tried to diable ssr for the Chart Component. There is no error in console.

Here is the default Next App with just this Chart Component

src/app/page.tsx

"use client";
import dynamic from 'next/dynamic';
const App = dynamic(() => import('@/components/ChartComponent').then(mod => mod.App), { ssr: false });

export default function Home(props) {
    return (
        <App></App>
    );
}

src/components/ChartComponent.tsx

"use client";
import { createChart, LineSeries, AreaSeries } from 'lightweight-charts';
import React, {
    createContext,
    forwardRef,
    useCallback,
    useContext,
    useEffect,
    useImperativeHandle,
    useLayoutEffect,
    useRef,
    useState,
} from 'react';

const Context = createContext();

const initialData = [
    { time: '2018-10-11', value: 52.89 },
    { time: '2018-10-12', value: 51.65 },
    { time: '2018-10-13', value: 51.56 },
    { time: '2018-10-14', value: 50.19 },
    { time: '2018-10-15', value: 51.86 },
    { time: '2018-10-16', value: 51.25 },
];

const initialData2 = [
    { time: '2018-10-11', value: 42.89 },
    { time: '2018-10-12', value: 41.65 },
    { time: '2018-10-13', value: 41.56 },
    { time: '2018-10-14', value: 40.19 },
    { time: '2018-10-15', value: 41.86 },
    { time: '2018-10-16', value: 41.25 },
];
const currentDate = new Date(initialData[initialData.length - 1].time);

export const App = props => {
    const {
        colors: {
            backgroundColor = 'white',
            lineColor = '#2962FF',
            textColor = 'black',
        } = {},
    } = props;

    const [chartLayoutOptions, setChartLayoutOptions] = useState({});
    // The following variables illustrate how a series could be updated.
    const series1 = useRef(null);
    const series2 = useRef(null);
    const [started, setStarted] = useState(false);
    const [isSecondSeriesActive, setIsSecondSeriesActive] = useState(false);

    // The purpose of this effect is purely to show how a series could
    // be updated using the `reference` passed to the `Series` component.
    useEffect(() => {
        if (series1.current === null) {
            return;
        }
        let intervalId;

        if (started) {
            intervalId = setInterval(() => {
                currentDate.setDate(currentDate.getDate() + 1);
                const next = {
                    time: currentDate.toISOString().slice(0, 10),
                    value: 53 - 2 * Math.random(),
                };
                series1.current.update(next);
                if (series2.current) {
                    series2.current.update({
                        ...next,
                        value: 43 - 2 * Math.random(),
                    });
                }
            }, 1000);
        }
        return () => clearInterval(intervalId);
    }, [started]);

    useEffect(() => {
        setChartLayoutOptions({
            background: {
                color: backgroundColor,
            },
            textColor,
        });
    }, [backgroundColor, textColor]);

    return (
        <>
            <button type="button" onClick={() => setStarted(current => !current)}>
                {started ? 'Stop updating' : 'Start updating series'}
            </button>
            <button type="button" onClick={() => setIsSecondSeriesActive(current => !current)}>
                {isSecondSeriesActive ? 'Remove second series' : 'Add second series'}
            </button>
            <Chart layout={chartLayoutOptions}>
                <Series
                    ref={series1}
                    type={'line'}
                    data={initialData}
                    color={lineColor}
                />
                {isSecondSeriesActive && <Series
                    ref={series2}
                    type={'area'}
                    data={initialData2}
                    color={lineColor}
                />}
            </Chart>
        </>
    );
};

export function Chart(props) {
    const [container, setContainer] = useState(false);
    const handleRef = useCallback(ref => setContainer(ref), []);
    return (
        <div ref={handleRef}>
            {container && <ChartContainer {...props} container={container} />}
        </div>
    );
}

export const ChartContainer = forwardRef((props, ref) => {
    const { children, container, layout, ...rest } = props;

    const chartApiRef = useRef({
        isRemoved: false,
        api() {
            if (!this._api) {
                this._api = createChart(container, {
                    ...rest,
                    layout,
                    width: container.clientWidth,
                    height: 300,
                });
                this._api.timeScale().fitContent();
            }
            return this._api;
        },
        free(series) {
            if (this._api && series) {
                this._api.removeSeries(series);
            }
        },
    });

    useLayoutEffect(() => {
        const currentRef = chartApiRef.current;
        const chart = currentRef.api();

        const handleResize = () => {
            chart.applyOptions({
                ...rest,
                width: container.clientWidth,
            });
        };

        window.addEventListener('resize', handleResize);
        return () => {
            window.removeEventListener('resize', handleResize);
            chartApiRef.current.isRemoved = true;
            chart.remove();
        };
    }, []);

    useLayoutEffect(() => {
        const currentRef = chartApiRef.current;
        currentRef.api();
    }, []);

    useLayoutEffect(() => {
        const currentRef = chartApiRef.current;
        currentRef.api().applyOptions(rest);
    }, []);

    useImperativeHandle(ref, () => chartApiRef.current.api(), []);

    useEffect(() => {
        const currentRef = chartApiRef.current;
        currentRef.api().applyOptions({ layout });
    }, [layout]);

    return (
        <Context.Provider value={chartApiRef.current}>
            {props.children}
        </Context.Provider>
    );
});
ChartContainer.displayName = 'ChartContainer';

export const Series = forwardRef((props, ref) => {
    const parent = useContext(Context);
    const context = useRef({
        api() {
            if (!this._api) {
                const { children, data, type, ...rest } = props;
                this._api =
                    type === 'line'
                        ? parent.api().addSeries(LineSeries, rest)
                        : parent.api().addSeries(AreaSeries, rest);
                this._api.setData(data);
            }
            return this._api;
        },
        free() {
            // check if parent component was removed already
            if (this._api && !parent.isRemoved) {
                // remove only current series
                parent.free(this._api);
            }
        },
    });

    useLayoutEffect(() => {
        const currentRef = context.current;
        currentRef.api();

        return () => currentRef.free();
    }, []);

    useLayoutEffect(() => {
        const currentRef = context.current;
        const { children, data, ...rest } = props;
        currentRef.api().applyOptions(rest);
    });

    useImperativeHandle(ref, () => context.current.api(), []);

    return (
        <Context.Provider value={context.current}>
            {props.children}
        </Context.Provider>
    );
});
Series.displayName = 'Series';

Apart from these two changes, there are no changes to any of the files after creating nextjs project. I'm using nextjs:15.1.7 & reactv19 & lightweight-charts:5.0.2

I tried updating to dynamic import and disable ssr, but did not work

Share Improve this question edited Feb 24 at 16:45 Drew Reese 203k17 gold badges239 silver badges270 bronze badges asked Feb 24 at 14:30 Raja SekarRaja Sekar 991 silver badge3 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

Please use the code hope it's will be fix.

src/app/page.tsx

// src/app/page.tsx
    import dynamic from "next/dynamic";
    import { Suspense } from "react";
    
    // Dynamically import App with SSR disabled
    const App = dynamic(
      () => import("@/components/ChartComponent").then((mod) => mod.App),
      { ssr: false }
    );
    
    export default function Home() {
      return (
        <div style={{ minHeight: "400px", padding: "20px" }}>
          <h1>Lightweight Charts Demo</h1>
          <Suspense fallback={<p>Loading chart...</p>}>
            <App />
          </Suspense>
        </div>
      );
    }

src/components/ChartComponent.tsx

 // src/components/ChartComponent.tsx
"use client"; // Mark as Client Component for Next.js App Router

import { createChart, IChartApi, ISeriesApi } from "lightweight-charts";
import React, {
  createContext,
  forwardRef,
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
} from "react";

// Chart context to share the chart API
const ChartContext = createContext<{
  api: () => IChartApi;
  free: (series: ISeriesApi<any>) => void;
} | null>(null);

// Sample data
const initialData = [
  { time: "2018-10-11", value: 52.89 },
  { time: "2018-10-12", value: 51.65 },
  { time: "2018-10-13", value: 51.56 },
  { time: "2018-10-14", value: 50.19 },
  { time: "2018-10-15", value: 51.86 },
  { time: "2018-10-16", value: 51.25 },
];

const initialData2 = [
  { time: "2018-10-11", value: 42.89 },
  { time: "2018-10-12", value: 41.65 },
  { time: "2018-10-13", value: 41.56 },
  { time: "2018-10-14", value: 40.19 },
  { time: "2018-10-15", value: 41.86 },
  { time: "2018-10-16", value: 41.25 },
];
const currentDate = new Date(initialData[initialData.length - 1].time);

// Main App component with controls
export const App = (props: {
  colors?: {
    backgroundColor?: string;
    lineColor?: string;
    textColor?: string;
  };
}) => {
  const {
    colors: {
      backgroundColor = "white",
      lineColor = "#2962FF",
      textColor = "black",
    } = {},
  } = props;

  const [chartLayoutOptions, setChartLayoutOptions] = useState({});
  const series1 = useRef<ISeriesApi<"Line"> | null>(null);
  const series2 = useRef<ISeriesApi<"Area"> | null>(null);
  const [started, setStarted] = useState(false);
  const [isSecondSeriesActive, setIsSecondSeriesActive] = useState(false);

  // Simulate real-time updates
  useEffect(() => {
    if (!series1.current || !started) return;

    const intervalId = setInterval(() => {
      currentDate.setDate(currentDate.getDate() + 1);
      const next = {
        time: currentDate.toISOString().slice(0, 10),
        value: 53 - 2 * Math.random(),
      };
      series1.current.update(next);
      if (series2.current) {
        series2.current.update({ ...next, value: 43 - 2 * Math.random() });
      }
    }, 1000);

    return () => clearInterval(intervalId);
  }, [started]);

  // Update chart layout options
  useEffect(() => {
    setChartLayoutOptions({
      background: { color: backgroundColor },
      textColor,
    });
  }, [backgroundColor, textColor]);

  return (
    <div>
      <button type="button" onClick={() => setStarted((current) => !current)}>
        {started ? "Stop updating" : "Start updating series"}
      </button>
      <button
        type="button"
        onClick={() => setIsSecondSeriesActive((current) => !current)}
      >
        {isSecondSeriesActive ? "Remove second series" : "Add second series"}
      </button>
      <Chart layout={chartLayoutOptions}>
        <Series
          ref={series1}
          type="Line"
          data={initialData}
          color={lineColor}
        />
        {isSecondSeriesActive && (
          <Series
            ref={series2}
            type="Area"
            data={initialData2}
            color={lineColor}
          />
        )}
      </Chart>
    </div>
  );
};

// Chart wrapper component
export function Chart(props: {
  children: React.ReactNode;
  layout: { background: { color: string }; textColor: string };
}) {
  const chartContainerRef = useRef<HTMLDivElement | null>(null);

  return (
    <div
      ref={chartContainerRef}
      style={{ width: "600px", height: "300px", position: "relative" }}
    >
      {chartContainerRef.current && (
        <ChartContainer {...props} container={chartContainerRef.current} />
      )}
    </div>
  );
}

// ChartContainer component
export const ChartContainer = forwardRef<
  IChartApi,
  {
    container: HTMLDivElement;
    layout: { background: { color: string }; textColor: string };
    children: React.ReactNode;
  }
>((props, ref) => {
  const { container, layout, children } = props;
  const chartApiRef = useRef<{
    _api?: IChartApi;
    api: () => IChartApi;
    free: (series: ISeriesApi<any>) => void;
  }>({
    api() {
      if (!this._api) {
        this._api = createChart(container, {
          width: container.clientWidth,
          height: container.clientHeight,
          layout,
        });
        this._api.timeScale().fitContent();
      }
      return this._api;
    },
    free(series) {
      if (this._api && series) {
        this._api.removeSeries(series);
      }
    },
  });

  useEffect(() => {
    const chart = chartApiRef.current.api();

    const handleResize = () => {
      chart.applyOptions({ width: container.clientWidth });
    };
    window.addEventListener("resize", handleResize);

    return () => {
      window.removeEventListener("resize", handleResize);
      if (chartApiRef.current._api) {
        chartApiRef.current._api.remove();
      }
    };
  }, [container]);

  useEffect(() => {
    const chart = chartApiRef.current.api();
    chart.applyOptions({ layout });
  }, [layout]);

  useImperativeHandle(ref, () => chartApiRef.current.api(), []);

  return (
    <ChartContext.Provider value={chartApiRef.current}>
      {children}
    </ChartContext.Provider>
  );
});
ChartContainer.displayName = "ChartContainer";

// Series component
export const Series = forwardRef<
  ISeriesApi<"Line"> | ISeriesApi<"Area">,
  {
    type: "Line" | "Area";
    data: { time: string; value: number }[];
    color: string;
    children?: React.ReactNode;
  }
>((props, ref) => {
  const { type, data, color, children } = props;
  const parent = useContext(ChartContext);
  if (!parent) throw new Error("Series must be used within a Chart");

  const seriesApiRef = useRef<{
    _api?: ISeriesApi<"Line"> | ISeriesApi<"Area">;
    api: () => ISeriesApi<"Line"> | ISeriesApi<"Area">;
    free: () => void;
  }>({
    api() {
      if (!this._api) {
        this._api =
          type === "Line"
            ? parent.api().addLineSeries({ color })
            : parent.api().addAreaSeries({ lineColor: color });
        this._api.setData(data);
      }
      return this._api;
    },
    free() {
      if (this._api) {
        parent.free(this._api);
      }
    },
  });

  useEffect(() => {
    const series = seriesApiRef.current.api();
    return () => seriesApiRef.current.free();
  }, []);

  useEffect(() => {
    const series = seriesApiRef.current.api();
    series.applyOptions({ color });
    series.setData(data);
  }, [color, data]);

  useImperativeHandle(ref, () => seriesApiRef.current.api(), []);

  return <ChartContext.Provider value={null}>{children}</ChartContext.Provider>;
});
Series.displayName = "Series";

本文标签: reactjsLightweight Charts not rendered in NextJS 15Stack Overflow