resium icon indicating copy to clipboard operation
resium copied to clipboard

useCesium() in Next.js always returns an empty object

Open Fedec96 opened this issue 3 years ago • 5 comments

Custom base viewer component + dynamic import:

src/components/Cesium/CesiumCore.tsx

import { Viewer } from "resium";
import { useProvider } from "../../hooks/useProvider";

export interface CesiumProps {
  children?: React.ReactNode | JSX.Element;
  style?: React.CSSProperties;
}

export default function CesiumCore({ children, style = {} }: CesiumProps) {
  const providers = useProvider();

  return (
    <Viewer
      requestRenderMode={true}
      homeButton={false}
      sceneModePicker={false}
      navigationHelpButton={false}
      baseLayerPicker={false}
      geocoder={false}
      fullscreenButton={false}
      animation={false}
      timeline={false}
      imageryProvider={providers.bingProvider}
      style={style}
    >
      {children || null}
    </Viewer>
  );
}

src/components/Cesium/index.tsx

import dynamic from "next/dynamic";
const Cesium = dynamic(() => import("./CesiumCore"), { ssr: false });
export default Cesium;

Actual page using dynamically imported component:

src/pages/index.tsx

import { useCesium } from "resium";
import { Button } from "@mantine/core";

import Cesium from "../components/Cesium";

export default function HomePage() {
  const cesium = useCesium();

  return (
    <Cesium>
      <Button
        sx={{ position: "absolute", top: 0, left: 0 }}
        onClick={() => {
          // Never entering, `cesium` is always `{}`
          console.warn(cesium);
          if (!cesium || !cesium.viewer) return;
          cesium.viewer.camera.flyHome();
        }}
      >
        Click me
      </Button>
    </Cesium>
  );
}

The object provided by Resium's context is always empty and I suspect this is because of SSR. Any workaround for this? Thanks!

Fedec96 avatar Apr 05 '23 11:04 Fedec96

I saw a similar issue with Next.js and SSR - in the end, I went back to a traditional (and original) useRef construct. That worked for me.

rrainey avatar Apr 23 '23 01:04 rrainey

Thanks for the reply. I ended up doing the same thing: I created a context where I provide a classic ref + setRef couple and then export the shortcut hook:

src/components/Cesium/CesiumCore.tsx

import { useEffect, useRef } from "react";
import { Viewer } from "resium";

import { useCesium } from "@/context/CesiumContext";

import type { Viewer as CesiumViewer } from "cesium";
import type { CesiumComponentRef } from "resium";

export interface CesiumProps {
  children?: React.ReactNode | JSX.Element;
  style?: React.CSSProperties;
}

export default function CesiumCore({ children, style = {} }: CesiumProps) {
  const ref = useRef<CesiumComponentRef<CesiumViewer>>(null);
  const { setRef } = useCesium();

  useEffect(() => setRef(ref), []);

  return (
    <Viewer ref={ref} style={style}>
      {children || null}
    </Viewer>
  );
}

src/context/CesiumContext.tsx

import { createContext, useContext, useEffect, useState } from "react";

import type { Viewer } from "cesium";
import type { CesiumComponentRef } from "resium";

import type { GlobalProps } from "@/types/misc";

export interface CesiumContextProps {
  cesium: Viewer;
  setRef: React.Dispatch<
    React.SetStateAction<React.MutableRefObject<CesiumComponentRef<Viewer>>>
  >;
}

export interface CesiumProviderProps {
  children: React.ReactNode | JSX.Element | React.ReactElement<GlobalProps>;
}

export const CesiumContext = createContext<CesiumContextProps>(null);

export const CesiumProvider = ({ children }: CesiumProviderProps) => {
  const [ref, setRef] =
    useState<React.MutableRefObject<CesiumComponentRef<Viewer>>>(null);

  const [cesium, setCesium] = useState<Viewer>(null);

  useEffect(
    () => ref?.current?.cesiumElement && setCesium(ref.current.cesiumElement),
    [ref]
  );

  return (
    <CesiumContext.Provider value={{ cesium, setRef }}>
      {children}
    </CesiumContext.Provider>
  );
};

export const useCesium = () => useContext(CesiumContext);

Maybe dirty, but works like a charm.

Fedec96 avatar Apr 24 '23 10:04 Fedec96

Resium works only in dynamic, so all components referering to Resium and Cesium should be rendered in dynamic, I think.

rot1024 avatar May 12 '23 03:05 rot1024

Thanks for the insight. Makes sense. For now I'll settle with my custom context, but it would be nice if Resium exposed the equivalent of setRef so that this extra step can be avoided -- just a suggestion. Maybe the issue won't even subsist when migrating to the app folder with the use of "use client".

Fedec96 avatar May 16 '23 06:05 Fedec96

I think Cesium cannot work in Node.js (server side) env, so it is hard to create a content of ref in server side. We can consider using getServerSideProps to pass parameters to initialize Cesium Viewer from server side to client side.

rot1024 avatar Jun 05 '23 09:06 rot1024