vasturiano / react-globe.gl

React component for Globe Data Visualization using ThreeJS/WebGL
https://vasturiano.github.io/react-globe.gl/example/world-population/
MIT License
818 stars 150 forks source link

Ref under nextjs dynamic import #147

Open beachstrider opened 9 months ago

beachstrider commented 9 months ago

Currently ref ( or even with forwardRef ) doesn't seem to work, here is my implementation:

'use client'

import { forwardRef, useLayoutEffect, useRef } from 'react'
import { GlobeProps } from 'react-globe.gl'

import dynamic from 'next/dynamic'

import placesData from './placesData.json'

const _Globe = dynamic(() => import('react-globe.gl').then((mod) => mod.default), {
  ssr: false
})

const Globe = forwardRef((props: GlobeProps, ref: any) => <_Globe {...props} ref={ref} />)

export const CitiesGlobe = () => {
  const globeEl = useRef<any>(null)
  const places: any = placesData

  useLayoutEffect(() => {
    if (globeEl.current) {
      globeEl.current.pointOfView({ lat: 20, lng: -16.6, altitude: 1.7 }, 0)
      globeEl.current.controls().autoRotate = true
      globeEl.current.controls().enabled = false
    }
  }, [])

  const handleVisibilityChange = () => {
    if (globeEl.current) {
      globeEl.current.controls().autoRotate = !document.hidden
    }
  }

  document.addEventListener('visibilitychange', handleVisibilityChange)

  return (
    <Globe
      ref={globeEl}
      width={520}
      height={520}
      globeImageUrl="//unpkg.com/three-globe/example/img/earth-night.jpg"
      backgroundColor="rgba(0,0,0,0)"
      labelsData={places}
      labelColor={() => 'rgba(255, 165, 0, 0.75)'}
      labelLat={(d: any) => d.properties.latitude}
      labelLng={(d: any) => d.properties.longitude}
      labelText={(d: any) => d.properties.name}
      labelSize={(d: any) => Math.sqrt(d.properties.pop_max) * 4e-4}
      labelDotRadius={(d: any) => Math.sqrt(d.properties.pop_max) * 4e-4}
      labelResolution={2}
      objectRotation={{ x: 50, y: 50, z: 1 }}
    />
  )
}
beachstrider commented 9 months ago

Hi @vasturiano I couldn't find any examples with nextjs, specifically using dynamic import, could you guide me?

vasturiano commented 9 months ago

This React component is framework agnostic, so there wouldn't be any on this repo. This appears to be an issue with nextjs rather than here, so perhaps a dedicated forum to nextjs would be more helpful.

gabrieletesser commented 9 months ago

Hi,

for you or others who may have this issue, I've solved it by wrapping Globe in its own component.

"use client";

import {useEffect, useRef, useState} from "react";
import Globe from "react-globe.gl";

export default function _Globe() {
  const markerSvg = `<svg viewBox="-4 0 36 36">
    <path fill="currentColor" d="M14,0 C21.732,0 28,5.641 28,12.6 C28,23.963 14,36 14,36 C14,36 0,24.064 0,12.6 C0,5.641 6.268,0 14,0 Z"></path>
    <circle fill="black" cx="14" cy="14" r="7"></circle>
  </svg>`;

  const [globeCenter, setGlobeCenter] = useState({lat: 0, lng: 0, altitude: 2});

  const globeEl = useRef(null);

  useEffect(() => {
    globeEl.current.pointOfView(globeCenter);
    globeEl.current.controls().enableZoom = false;
  }, [globeEl.current]);

  useEffect(() => {
    console.log("REF", globeEl);
  }, [globeEl]);

  return (
      <Globe
        height={500}
        ref={globeEl}
        backgroundColor="rgba(0,0,0,0)"
        htmlElementsData={[{lat: "50.073658", lng: "14.418540", color: "white", size: "35", label: "Prague"}]}
        htmlElement={(d) => {
          const el = document.createElement("div");
          el.innerHTML = markerSvg;
          el.style.color = d.color;
          el.style.width = `${d.size}px`;

          el.style["pointer-events"] = "auto";
          el.style.cursor = "pointer";
          el.onclick = () => globeEl.current.pointOfView({lat: d.lat, lng: d.lng}, 750);
          return el;
        }}
        pointOfView={{lat: 50.073658, lng: 14.41854}}
        globeImageUrl="//unpkg.com/three-globe/example/img/earth-blue-marble.jpg"
      />
  );
}

and then dynamically importing this disabling ssr into the main component.


const Globe = dynamic(() => import("./Globe").then((mod) => mod.default), {
  ssr: false,
  loading: () => (
    <div className="w-full h-[500px] flex justify-center items-center">
      <span>Loading the world</span>
    </div>
  ),
});

and then just using it normally

export default function Home() {
  return(
  <div>

    {/* some JSX */}

    <Globe />

    {/* some more JSX stuff */}

  </div>
  )
}

Hope it helps!

clkefe commented 8 months ago

Yep, this works! Thank you so much!!