How to Completely Remove vtkFullScreenRenderWindow Rendering Before Page Navigation in React?

Hello
I am currently working on a React project where I use VTK to display medical images. I am able to visualize my images successfully, but since I use vtkFullScreenRenderWindow, the rendering window creates a new div in my page. The problem is that when I navigate to another page, this div remains present.

Does anyone know how I can completely remove my renderings before navigating to another page?
Here a glimpse of what I am urrently doing in my code

useEffect(() => {
const initializeVTK = async () => {
if (!renderWindowRef.current) {
setError(‘Erreur: renderWindowRef.current est null.’);
return;
}

  if (!renderWindowRef.current.getRenderer) {
    renderWindowRef.current = vtkFullScreenRenderWindow.newInstance({
      parentElement: renderWindowRef.current,
      background: [0.1, 0.1, 0.1],
    });
  }

  const renderer = renderWindowRef.current.getRenderer();
  const renderWindow = renderWindowRef.current.getRenderWindow();

  if (!renderer || !renderWindow) {
    setError('Erreur d\'initialisation VTK');
    return;
  }

  try {
    const polyDataReader = vtkPolyDataReader.newInstance();
    const brainUrl = `/Data/Data/${patient_id}/MR-T1_Brain.vtk`;

    await polyDataReader.setUrl(brainUrl);

    polyDataReader.loadData().then(() => {
      const brainData = polyDataReader.getOutputData();

      const brainMapper = vtkMapper.newInstance();
      brainMapper.setInputData(brainData);

      const brainActor = vtkActor.newInstance();
      brainActor.setMapper(brainMapper);
      renderer.addActor(brainActor);

      coordinates.forEach(({ x, y, z }) => {
        const sphereSource = vtkSphereSource.newInstance({
          center: [x, y, z],
          radius: 5.0,
          phiResolution: 32,
          thetaResolution: 32,
        });

        const sphereMapper = vtkMapper.newInstance();
        sphereMapper.setInputConnection(sphereSource.getOutputPort());

        const sphereActor = vtkActor.newInstance();
        sphereActor.setMapper(sphereMapper);
        sphereActor.getProperty().setColor(0.0, 0.0, 1.0);
        renderer.addActor(sphereActor);
      });

      secondCoordinates.forEach(({ x, y, z }) => {
        const sphereSource = vtkSphereSource.newInstance({
          center: [x, y, z],
          radius: 5.0,
          phiResolution: 32,
          thetaResolution: 32,
        });

        const sphereMapper = vtkMapper.newInstance();
        sphereMapper.setInputConnection(sphereSource.getOutputPort());

        const sphereActor = vtkActor.newInstance();
        sphereActor.setMapper(sphereMapper);
        sphereActor.getProperty().setColor(1.0, 0.0, 0.0);
        renderer.addActor(sphereActor);
      });

      renderer.resetCamera();
      renderWindow.render();
    });
  } catch (error) {
    console.error('Erreur lors du chargement du modèle:', error);
    setError('Erreur lors du chargement du modèle VTK.');
  }
};

if (coordinates.length > 0 && secondCoordinates.length > 0) {
  initializeVTK();
}

}, [coordinates, secondCoordinates, patient_id]);

// Fonction de gestion du clic sur le bouton
const handleNextStep = () => {
// Nettoyer la scène et supprimer la fenêtre d’affichage
if (renderWindowRef.current) {
const renderer = renderWindowRef.current.getRenderer();
const renderWindow = renderWindowRef.current.getRenderWindow();

  if (renderer && renderWindow) {
    renderer.removeAllViewProps(); // Supprimer tous les objets
    renderWindow.render(); // Rafraîchir la vue
  }

  // Supprimer complètement la fenêtre d'affichage
  renderWindowRef.current.delete();
  renderWindowRef.current = null; // Libérer la référence
}

// Naviguer vers la page suivante
navigate('/bilan', {
  state: { patient_id, user_id, taskStatus }, // Passage des données à la page suivante
});

};

Out of curiosity, why do you use vtkFullScreenRenderWindow when you aim to use a vtkRenderWindow inside a component (react) that is not FullScreen?

I guess, that’s our fault as we don’t properly cover that part in the documentation and even removed the only example covering the proper way to initialize a vtkRenderWindow while “improving” the doc. (sorry about that)

Long story short vtkFullScreenRenderWindow is just a helper class that was designed for example to reduce the demo code to the meaningful part. But in truth, you should do what the class vtkFullScreenRenderWindow is doing internally inside a react or vue component.

You can find a vue or react example of vtk.js integration, unfortunately what we did is probably more involve that it needs to be for your usage.

I use vtkFullScreenRendererWindow because when I first try to use vtkRendererWindow ,I could see the images but it seems like it’s fixed (It does not move just like a picture).Here is the code

import React, { useEffect, useRef, useState } from ‘react’;
import { useLocation, useNavigate } from ‘react-router-dom’;
import vtkRenderWindow from ‘@kitware/vtk.js/Rendering/Core/RenderWindow’;
import vtkRenderer from ‘@kitware/vtk.js/Rendering/Core/Renderer’;
import vtkRenderWindowInteractor from ‘@kitware/vtk.js/Rendering/Core/RenderWindowInteractor’;
import vtkOpenGLRenderWindow from ‘@kitware/vtk.js/Rendering/OpenGL/RenderWindow’;
import vtkPolyDataReader from ‘@kitware/vtk.js/IO/Legacy/PolyDataReader’;
import vtkMapper from ‘@kitware/vtk.js/Rendering/Core/Mapper’;
import vtkActor from ‘@kitware/vtk.js/Rendering/Core/Actor’;

const RenderVTKPolyDataSkin = () => {
const renderWindowRef = useRef(null);
const ws = useRef(null);
const location = useLocation();
const navigate = useNavigate();
const { patient_id, user_id, statuses } = location.state || {};
const [taskStatus, setTaskStatus] = useState(statuses || {});

useEffect(() => {
if (!patient_id || !user_id) {
console.error(‘Les IDs patient ou utilisateur sont manquants.’);
return;
}

// Créer les instances VTK
const renderWindow = vtkRenderWindow.newInstance();
const renderer = vtkRenderer.newInstance({ background: [0.1, 0.1, 0.1] });
const openGLRenderWindow = vtkOpenGLRenderWindow.newInstance();
const interactor = vtkRenderWindowInteractor.newInstance();

renderWindow.addRenderer(renderer);
renderWindow.addView(openGLRenderWindow);

// Lier le conteneur du rendu à OpenGLRenderWindow
openGLRenderWindow.setContainer(renderWindowRef.current);

// Lier l'interacteur
interactor.setView(openGLRenderWindow);
interactor.initialize();
interactor.bindEvents(renderWindowRef.current);

// Charger le fichier VTK
const reader = vtkPolyDataReader.newInstance();
const fileUrl = `/Data/Data/${patient_id}/MR-T1_Skin.vtk`;

reader
  .setUrl(fileUrl)
  .then(() => {
    const polyData = reader.getOutputData(0);
    if (!polyData) {
      console.error('Les données PolyData sont vides.');
      return;
    }

    const mapper = vtkMapper.newInstance();
    mapper.setInputData(polyData);

    const actor = vtkActor.newInstance();
    actor.setMapper(mapper);

    renderer.addActor(actor);
    renderer.resetCamera(); // Réinitialiser la caméra pour s'assurer que tout est visible
    renderWindow.render();

    // Re-rendre l'écran à chaque animation
    interactor.onStartAnimation(() => renderWindow.render());
    interactor.onEndAnimation(() => renderWindow.render());
  })
  .catch((err) => {
    console.error('Erreur lors du chargement du fichier VTK:', err);
  });

// Fonction de nettoyage pour libérer les ressources
return () => {
  interactor.delete();
  openGLRenderWindow.delete();
  renderWindow.delete();
};

}, [patient_id, user_id]);

const handleResponse = (responseType) => {
console.log(Réponse utilisateur: ${responseType});

if (renderWindowRef.current) {
  renderWindowRef.current.style.display = 'none'; 
}

setTimeout(() => {
  navigate('/rendercerveau', { state: { patient_id, user_id, statuses: taskStatus } });
}, 100);

};

return (
<div
style={{
width: ‘100vw’,
height: ‘100vh’,
background: ‘#000’,
position: ‘relative’,
}}
>
{/* Conteneur du rendu VTK */}
<div
ref={renderWindowRef}
style={{
width: ‘100%’,
height: ‘calc(100vh - 150px)’,
}}
/>

  {/* Panneau de contrôle */}
  <div
    style={{
      position: 'absolute',
      bottom: '0px',
      left: '50%',
      transform: 'translateX(-50%)',
      width: '30%',
      height: '120px',
      backgroundColor: 'white',
      borderRadius: '10px',
      boxShadow: '0px -2px 10px red',
      zIndex: 5,
      overflow: 'hidden',
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      alignItems: 'center',
      padding: '10px',
    }}
  >
    <div
      style={{
        color: 'grey',
        fontSize: '19px',
        fontWeight: 'bold',
        marginBottom: '25px',
      }}
    >
      Êtes-vous satisfait de la qualité du traitement ?
    </div>

    <div
      style={{
        display: 'flex',
        flexDirection: 'row',
        justifyContent: 'space-around',
        width: '100%',
      }}
    >
      <span
        onClick={() => handleResponse('Non, Annuler')}
        style={{
          color: '#1BBC9B',
          fontSize: '16px',
          fontWeight: 'bold',
          cursor: 'pointer',
        }}
      >
        NON, ANNULER
      </span>
      <span
        onClick={() => handleResponse('Oui, Continuer')}
        style={{
          color: '#1BBC9B',
          fontSize: '16px',
          fontWeight: 'bold',
          cursor: 'pointer',
        }}
      >
        OUI, CONTINUER
      </span>
    </div>
  </div>
</div>

);
};

export default RenderVTKPolyDataSkin;

Maybe I did something wrong.

You are missing the setup of the interactor style.