USER
The globe component renders just fine on initial mount but it never remounts with the arcs when i navigate to a different pageProp and come back to the pageProp with the globe component: GlobeComponent.jsx: import React, { useEffect, useRef } from 'react';
import { WebGLRenderer, Scene, AmbientLight, DirectionalLight, Color, Fog, PerspectiveCamera, PointLight } from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { createGlowMesh } from 'three-glow-mesh';
import ThreeGlobe from "three-globe";
import countries from './files/globe-data-min.json';
import travelHistory from './files/my-flights.json';
import airportHistory from './files/my-airports.json';
let mouseX = 0;
let mouseY = 0;
let timeoutId;
let renderer, camera, scene, controls;
let Globe;
let frameId;
const GlobeComponent = ({ globeWidth, globeHeight, windowWidth, windowHeight, uniqueValue }) => {
const containerRef = useRef();
const prevUniqueValueRef = useRef();
let windowHalfX = windowWidth / 2;
let windowHalfY = windowHeight / 2;
// Event listeners
function onWindowResize() {
camera.aspect = windowWidth / windowHeight;
camera.updateProjectionMatrix();
windowHalfX = windowWidth;
windowHalfY = windowHeight;
renderer.setSize(windowWidth, windowHeight);
}
function onMouseMove(event) {
mouseX = event.clientX - windowHalfX;
mouseY = event.clientY - windowHalfY;
// console.log("x: " + mouseX + " y: " + mouseY);
}
// Animation
function animate() {
camera.lookAt(scene.position);
controls.update();
renderer.render(scene, camera);
frameId = requestAnimationFrame(animate);
}
useEffect(() => {
// Initialize core ThreeJS elements
function init() {
// Initialize renderer
renderer = new WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(globeWidth, globeHeight);
renderer.setClearColor(0x000000, 0);
containerRef.current.appendChild(renderer.domElement);
// Initialize scene, light
scene = new Scene();
scene.add(new AmbientLight(0xbbbbbb, 0.4));
// Initialize camera, light
camera = new PerspectiveCamera();
camera.aspect = globeWidth / globeHeight;
camera.updateProjectionMatrix();
var dLight = new DirectionalLight(0xffffff, 0.8);
dLight.position.set(-800, 2000, 400);
camera.add(dLight);
var dLight1 = new DirectionalLight(0x7982f6, 1);
dLight1.position.set(-200, 500, 200);
camera.add(dLight1);
var dLight2 = new PointLight(0x8566cc, 0.5);
dLight2.position.set(-200, 500, 200);
camera.add(dLight2);
camera.position.z = 400;
camera.position.x = 0;
camera.position.y = 0;
scene.add(camera);
// Additional effects
scene.fog = new Fog(0x535ef3, 400, 2000);
// Helpers
// const axesHelper = new THREE.AxesHelper(800);
// scene.add(axesHelper);
// var helper = new THREE.DirectionalLightHelper(dLight);
// scene.add(helper);
// var helperCamera = new THREE.CameraHelper(dLight.shadow.camera);
// scene.add(helperCamera);
// Initialize controls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dynamicDampingFactor = 0.01;
controls.enablePan = false;
controls.minDistance = Math.min(globeWidth, globeHeight) / 2;
controls.maxDistance = Math.min(globeWidth, globeHeight) / 2;
controls.rotateSpeed = 0.8;
controls.zoomSpeed = 1;
controls.autoRotate = false;
controls.minPolarAngle = Math.PI / 3.5;
controls.maxPolarAngle = Math.PI - Math.PI / 3;
}
// Initialize the Globe
function initGlobe() {
// Initialize the Globe
Globe = new ThreeGlobe({
waitForGlobeReady: true,
animateIn: true,
})
.hexPolygonsData(countries.features)
.hexPolygonResolution(3)
.hexPolygonMargin(0.7)
.showAtmosphere(true)
.atmosphereColor("#66ffffff")
.atmosphereAltitude(0.1)
.hexPolygonColor((e) => {
if (
["KEN", "CHN", "FRA", "ZAF", "JPN", "USA", "AUS", "CAN"].includes(
e.properties.ISO_A3
)
) {
return "rgba(255,255,255, 1)";
} else return "rgba(255,255,255, 0.5)";
});
// NOTE Arc animations are followed after the globe enters the scene
timeoutId = setTimeout(() => {
Globe.arcsData(travelHistory.flights)
.arcColor((e) => {
return e.status ? "#9cff00" : "#ff2e97";
})
.arcAltitude((e) => {
return e.arcAlt;
})
.arcStroke((e) => {
return e.status ? 0.5 : 0.3;
})
.arcDashLength(0.9)
.arcDashGap(4)
.arcDashAnimateTime(1000)
.arcsTransitionDuration(1000)
.arcDashInitialGap((e) => e.order * 1)
.labelsData(airportHistory.airports)
.labelColor(() => "#ffffff")
.labelDotOrientation((e) => {
return e.text === "NGA" ? "top" : "right";
})
.labelDotRadius(0.35)
.labelSize((e) => e.size)
.labelText("city")
.labelResolution(6)
.labelAltitude(0.01)
.pointsData(airportHistory.airports)
.pointColor(() => "#ffffff")
.pointsMerge(true)
.pointAltitude(0.07)
.pointRadius(0.10);
}, 1000);
Globe.rotateX(-Math.PI * (1 / 50));
Globe.rotateY(-Math.PI * (1 / 9));
Globe.rotateZ(-Math.PI / 60);
const globeMaterial = Globe.globeMaterial();
globeMaterial.color = new Color(0x3a228a);
globeMaterial.emissive = new Color(0x220038);
globeMaterial.emissiveIntensity = 0.1;
globeMaterial.shininess = 0.7;
// NOTE Cool stuff
// globeMaterial.wireframe = true;
scene.add(Globe);
}
init();
initGlobe();
onWindowResize();
animate();
window.addEventListener('resize', onWindowResize, false);
renderer.domElement.addEventListener('mousemove', onMouseMove);
console.log("Initializing globe animation");
return () => {
clearTimeout(timeoutId);
window.removeEventListener('resize', onWindowResize);
renderer.domElement.removeEventListener('mousemove', onMouseMove);
cancelAnimationFrame(frameId);
if (containerRef.current) {
// Perform more thorough clean-up here
// Dispose of the scene's children
while (scene.children.length > 0) {
const object = scene.children[0];
if (object.dispose) {
object.dispose();
}
scene.remove(object);
}
// Dispose of the renderer and any associated resources
renderer.dispose();
// Dispose of any controls, if necessary
if (controls.dispose) controls.dispose();
if (renderer.domElement.parentNode === containerRef.current) {
// Remove the renderer DOM element
containerRef.current.removeChild(renderer.domElement);
}
}
};
}, [uniqueValue]);
return <div ref={containerRef} />;
};
export default GlobeComponent; _app.jsx: import { AnimatePresence, motion } from "framer-motion";
import { useRouter } from "next/router";
import React, { useEffect, useState } from 'react';
import Layout from "../components/Layout";
import Transition from "../components/Transition";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
const router = useRouter();
const [uniqueValue, setUniqueValue] = useState('');
useEffect(() => {
// Generate a unique value whenever pageProps or router.route changes
setUniqueValue( Date.now().toString());
}, [router.route]);
return (
<Layout>
<AnimatePresence mode="wait">
<motion.div key={router.route} className="h-full">
<Transition />
<Component {...pageProps} uniqueValue={uniqueValue} />
</motion.div>
</AnimatePresence>
</Layout>
);
}
export default MyApp;ASSISTANT
Based on your code, you are using `uniqueValue` as a dependency for your `useEffect` hook within `GlobeComponent.jsx`. This suggests that when `uniqueValue` changes (which happens every time the `router.route` changes), the `useEffect` runs again, re-initializing your globe.
But if you find that the arcs do not re-appear after navigating away and coming back, there might be a couple of potential issues:
1. The cleanup function may not be sufficient to reset the globe state properly.
2. The initialization functions (`init` and `initGlobe`) may have side effects that are not being reset or cleared properly.
3. Your timeout for arcs creation and your related logic might not be getting executed in the correct order on re-mount.
Given this, here are a few steps you can take to diagnose and fix the issue:
### Ensure Proper Cleanup
Make sure your cleanup function not only removes event listeners and cancels the animation frame but also properly disposes of Three.js objects (meshes, materials, textures, etc.) to avoid unintended side effects when the component is remounted.
Example:
```javascript
// ...Inside cleanup function
if (Globe) {
scene.remove(Globe);
// Dispose of any geometries, materials, textures if Globe has such properties
}
```
### Verify Arcs Initialization
Ensure that your logic for initializing arcs is being correctly triggered every time the component mounts. You might want to wrap the arcs initialization within a function that gets called after the globe is ready.
Example:
```javascript
function addArcsToGlobe() {
// Your logic to add arcs to the Globe
}
```
Make sure `addArcsToGlobe()` is called within your effect when the globe is guaranteed to be ready.
### Conditional Effect Execution
If the globe itself doesn't need to be re-initialized every time `uniqueValue` changes, you can create a separate `useEffect` that only deals with updating the arcs when `uniqueValue` changes, while the globe initialization can be part of a different `useEffect` that only runs on initial mount.
### Debug Logging
Add more `console.log` statements in critical parts of your code to get insights into the execution flow when the component remounts. Log information about the state of key objects and data.
### Check Arcs Data
Double-check that `travelHistory.flights` and `airportHistory.airports` always have valid arc data when the component remounts. It's possible that the data source becomes unavailable or changes in a way that prevents the arcs from rendering correctly.
Lastly, you might consider abstracting some of the initialization and cleanup logic into separate functions or custom hooks to make your component more maintainable and debuggable. The goal is to have a clear understanding of when each piece of the initialization and cleanup code is running in relation to the component lifecycle.