How to use Vanilla JS in React/Next.js?

Hi Friends!

Can someone let me know how to use Vanilla JS in React/Next.js?

I created an Image Gallerie with Vanilla JS and like to reuse the code in React/Next.js without changing something, is this possible?

I tried a few methods, but non of them are working and it seems tlike I just messed up some basic React understanding again…

I tried to wrap the Vanilla JS Code inside a function which is waiting until the DOM Content is loaded so that JavaScript has access to the DOM Elements. This is my code:

export default function ImageGalleryScript() {
  if (typeof window === 'object') {
    // Check if document is finally loaded
    document.addEventListener('DOMContentLoaded', function () {

*** VANILLA JS CODE***
});
  }}

It is not working because document is not defined.

What am I doing wrong?

document Is a object available only on Bowser side , may be next js is generating a html page by converting react and jsx to html, but you might have used document object in that flow somewhere try to use document , windows object (related code) by sending over as cdn

Can you be a bit more specific please? I still dont know how to go on…

Honestly, it is hard to tell anything without more of the code.

This could help

1 Like

I thought it is not neccessary to show the whole code. All I try to do is to wait until the DOM Content is loaded and then use Vanilla JS Code inside a React Function.

Unfortunatly your link doesn´t help, I already read this before and I tried a few method but non of them is working. Maybe because I use it in the wrong way.

I show you the whole code and hopefully you can see what I did wrong.

This is the Vanilla JS Code for the Image Gallerie:

const closeLightboxAndRemoveImage = () => {
      // Wenn Lightbox geschlossen werden Eventlistener deaktiviert
      document.removeEventListener('keydown', handleKeydown); // Schließt den addEventListener Keyboard
      document.removeEventListener('wheel', handleMousewheel); // Schließt den addEventListener Mousewheel

      document.querySelector('.lightbox-image').src = ''; // Löscht den Pfad des original Images
      document.querySelector('.lightbox').classList.remove('visible'); // Deaktiviert die Lightbox komplett
    };

    const showNextImage = () => {
      let allImages = Array.from(document.querySelectorAll('.galerie img')); // Speichert das Arrey in der variablen allImages
      let numberOfImages = allImages.length; // Anzahl der Images im Arrey wird gespeichert

      let currentImageFilename = document.querySelector('.lightbox-image').src; // Der Flad des Images wird aufgerufen und in der nächsten Zeile gespilttet
      currentImageFilename = currentImageFilename.split('/').pop(); // Pflad wird gesplittet und über die .pop funcktion wird der letzte Teil des Pfades nach dem Slash (/) gelöscht und neu hinzugefügt

      let currentImage = document.querySelector(
        `.galerie img[src*="${currentImageFilename}"]`
      ); // Pfad des aktuellen Images wird selectiert
      let currentImageIndex = allImages.indexOf(currentImage); // Der Index von de aktuellen Image wird ermittelt

      let nextImage = ''; // Neue Veriable mit leerem Speicherplatz wird erstellt

      let index = '';

      if (currentImageIndex + 1 >= numberOfImages) {
        // Wenn der aktuelle Index den letzten Wert erreichert dann öffne wieder das erste Image
        nextImage = allImages[0].src;
        index = 1;
      } else {
        // Wenn der Index nicht au den letzten Wert zeigt, dann springt es zu dem nächsten Wert
        nextImage = allImages[currentImageIndex + 1].src;
        index = currentImageIndex + 2;
      }

      document.querySelector('.lightbox-image').src =
        './images/originals/' + nextImage.split('/').pop(); // Quelle(Link) des Bildes in der Lightboy wird geändert

      document.querySelector('.lightbox-index').innerHTML =
        index + ' / ' + numberOfImages;
    };

    const showPreviousImage = () => {
      let allImages = Array.from(document.querySelectorAll('.galerie img'));
      let numberOfImages = allImages.length;

      let currentImageFilename = document.querySelector('.lightbox-image').src;
      currentImageFilename = currentImageFilename.split('/').pop();

      let currentImage = document.querySelector(
        `.galerie img[src*="${currentImageFilename}"]`
      );
      let currentImageIndex = allImages.indexOf(currentImage);

      let nextImage = '';

      if (currentImageIndex - 1 < 0) {
        nextImage = allImages[numberOfImages - 1].src;
        index = numberOfImages;
      } else {
        nextImage = allImages[currentImageIndex - 1].src;
        index = currentImageIndex;
      }

      document.querySelector('.lightbox-image').src =
        './images/originals/' + nextImage.split('/').pop();

      document.querySelector('.lightbox-index').innerHTML =
        index + ' / ' + numberOfImages;
    };

    // handleClickOnImage

    const handleClickOnImage = (event) => {
      // Parameter des addEventListeners (beinhaltet das Event)
      let image = event.target.parentElement.querySelector('img');
      let filename = image.src.split('/').pop(); // Splittet den Pfad des Images // .pop greift auf das letzte ELement im Pfad zu nachdem Slahs/

      let currentImage = document.querySelector(
        `.galerie img[src*="${filename}"]`
      );

      let allImages = Array.from(document.querySelectorAll('.galerie img'));
      let currentImageIndex = allImages.indexOf(currentImage);

      let numberOfImages = allImages.length;
      document.querySelector('.lightbox-index').innerHTML =
        currentImageIndex + ' / ' + numberOfImages;

      document.querySelector('.lightbox-image').src =
        './images/originals/' + filename; // Nuer Pfad des Images mit Originalgröße
      document.querySelector('.lightbox').classList.add('visible'); // Fügt die Classe "visible" and die Lightbox > Lightbox wird geöffnet

      document.addEventListener('keydown', handleKeydown); // Wenn Lightbox geöffnet ist, wird Keyboardsteuerung aktiviert
      document.addEventListener('wheel', handleMousewheel);
    };

    // Keyboardsteuerung

    const handleKeydown = (event) => {
      if (event.key === 'ArrowLeft' || event.key === 'a') {
        // Wenn Pfeiltaste link (oder verknüpfter Buchstaben) gedrückt wird das vorherige Bild angezeigt
        showPreviousImage();
      } else if (
        event.key === 'ArrowRight' ||
        event.key === 'd' ||
        event.key === ' '
      ) {
        // Wenn Pfeiltaste rechts (oder verknüpfter Buchstaben bzw Leertaste) gedrückt wird das nächste Bild angezeigt
        showNextImage();
      } else if (event.key === 'Escape') {
        // Bei gedrückter ESC Taste wird Image Gallery geschlossen
        closeLightboxAndRemoveImage();
      }
    };

    // Mousewheelsteuerung

    const handleMousewheel = (event) => {
      // Beim scrollen des Mausrads wird das nächste bzw vorherige Image angezeigt
      if (event.wheelDelta > 0) {
        showPreviousImage();
      } else {
        showNextImage();
      }
    };

    // Ausführende EventListener

    document.addEventListener('DOMContentLoaded', () => {
      // Funktion wird ausgeführt nachdem der DOM Content geladen wurde

      Array.from(document.querySelectorAll('.galerie-img')).forEach(
        // addEventListener greift auf das Arrey zu und startet einen forEach loop
        (img) => img.addEventListener('click', handleClickOnImage) // Bei click wird die Funktion handleClickOnImage ausgeführt
      );

      document
        .querySelector('.lightbox-image')
        .addEventListener('click', closeLightboxAndRemoveImage);
      document
        .querySelector('.lightbox-next')
        .addEventListener('click', showNextImage);
      document
        .querySelector('.lightbox-previous')
        .addEventListener('click', showPreviousImage);

I try to use this code inside a React Function, but because Next.js is server side rendered I need wrap another function around the Vanilla JS Code to tell that this code will just get executed when the DOM Content is loaded.

Thats what I tried:

export default function ImageGallerie() {

if (typeof window === 'object') {
// Check if document is finally loaded
   document.addEventListener("DOMContentLoaded", function () {

*** VANILLA JAVASCRIPT CODE FOR THE IMAGE GALLERIE ***

     });
  }

return (
<>
*** JSX FOR IMAGE GALLERIE ***
</>
)
}

This is another method I tried:

import { useEffect } from 'react';

export default function ImageGallerie() {

  useEffect(() => {

*** VANILLA JAVASCRIPT CODE FOR THE IMAGE GALLERIE ***

    alert('Finished loading');
  }, []);

return (
<>
*** JSX FOR IMAGE GALLERIE ***
</>
)
}

Probably I messed up the use Effect method, because I am not sure where exactly I need to paste the Vanilla JS Code and if I need to add another parameter.

I also tried a few other things, but these are out of my understanding, so it was just trial and error. However it should be really simple, because it is a common issue when working with React/Next.js

Hopefully someone can help.

Thank you!!

You are trying to run DOM methods on arbitrary JS functions (in this case functions created/used by React), which won’t work. DOM methods are used for interacting with the DOM in a browser.

JSX describes how you want a given part of the DOM to render, it isn’t HTML. You need to use refs to grab hold of a reference to the end result rendered in browser. At that point the reference will point at DOM objects that you can use DOM functions on.

Edit: note that it would be easier to just move all of the DOM manipulation part into React, this is just a hack

2 Likes
import { useEffect } from 'react';

export default function ImageGallerie() {

  useEffect(() => {
console.log(document);
#does this print document object in your browser?
#it is not  giving me error on compiling with it 
  }, []);

return (
<>
*** JSX FOR IMAGE GALLERIE ***
</>
)
}

Yes, but anything defined using JSX is very likely not going to exist, you have to use refs if you want to do what OP is trying to do in this case (and just to reiterate, this is a bad usecase). The document will exist at the point it’s called because the React app is mounted on a node in the document. What may not exist are the elements OP then tries to select (the elements might, but there is absolutely no guarantee of that if that excised JSX does contain things that OP wants to select using imperative methods).

1 Like

Thank you for your answers!

Please don’t blame me if I don’t understand it yet. I read the docs and watched a tutorial, but still miss some understandig whats happening under the hood.

I tried to make it work with useRef, but I get still the same error and I find it really difficult to get an understanding about it.

@b-bhupendra Yes it prints the document inside the console, but it is not working.

This is my code:

// IMAGE GALLERY EXAMPLE
// USe Vanilla JS in React/Next.js

import { useEffect, useRef } from 'react';
import React from 'react';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();

    const node = this.myRef.current;

    // **************************************************************************** Vanilla JS Start

    const closeLightboxAndRemoveImage = () => {
      document.removeEventListener('keydown', handleKeydown);
      document.removeEventListener('wheel', handleMousewheel);

      document.querySelector('.lightbox-image').src = '';
      document.querySelector('.lightbox').classList.remove('visible');
    };

    const showNextImage = () => {
      let allImages = Array.from(document.querySelectorAll('.galerie img'));
      let numberOfImages = allImages.length;

      let currentImageFilename = document.querySelector('.lightbox-image').src;
      currentImageFilename = currentImageFilename.split('/').pop();

      let currentImage = document.querySelector(
        `.galerie img[src*="${currentImageFilename}"]`
      );
      let currentImageIndex = allImages.indexOf(currentImage);

      let nextImage = '';

      let index = '';

      if (currentImageIndex + 1 >= numberOfImages) {
        nextImage = allImages[0].src;
        index = 1;
      } else {
        nextImage = allImages[currentImageIndex + 1].src;
        index = currentImageIndex + 2;
      }

      document.querySelector('.lightbox-image').src =
        './images/originals/' + nextImage.split('/').pop();

      document.querySelector('.lightbox-index').innerHTML =
        index + ' / ' + numberOfImages;
    };

    const showPreviousImage = () => {
      let allImages = Array.from(document.querySelectorAll('.galerie img'));
      let numberOfImages = allImages.length;

      let currentImageFilename = document.querySelector('.lightbox-image').src;
      currentImageFilename = currentImageFilename.split('/').pop();

      let currentImage = document.querySelector(
        `.galerie img[src*="${currentImageFilename}"]`
      );
      let currentImageIndex = allImages.indexOf(currentImage);

      let nextImage = '';

      if (currentImageIndex - 1 < 0) {
        nextImage = allImages[numberOfImages - 1].src;
        index = numberOfImages;
      } else {
        nextImage = allImages[currentImageIndex - 1].src;
        index = currentImageIndex;
      }

      document.querySelector('.lightbox-image').src =
        './images/originals/' + nextImage.split('/').pop();

      document.querySelector('.lightbox-index').innerHTML =
        index + ' / ' + numberOfImages;
    };

    const handleClickOnImage = (event) => {
      let image = event.target.parentElement.querySelector('img');
      let filename = image.src.split('/').pop();

      let currentImage = document.querySelector(
        `.galerie img[src*="${filename}"]`
      );

      let allImages = Array.from(document.querySelectorAll('.galerie img'));
      let currentImageIndex = allImages.indexOf(currentImage);

      let numberOfImages = allImages.length;
      document.querySelector('.lightbox-index').innerHTML =
        currentImageIndex + ' / ' + numberOfImages;

      document.querySelector('.lightbox-image').src =
        './images/originals/' + filename;
      document.querySelector('.lightbox').classList.add('visible');

      document.addEventListener('keydown', handleKeydown);
      document.addEventListener('wheel', handleMousewheel);
    };

    const handleKeydown = (event) => {
      if (event.key === 'ArrowLeft' || event.key === 'a') {
        showPreviousImage();
      } else if (
        event.key === 'ArrowRight' ||
        event.key === 'd' ||
        event.key === ' '
      ) {
        showNextImage();
      } else if (event.key === 'Escape') {
        closeLightboxAndRemoveImage();
      }
    };

    const handleMousewheel = (event) => {
      if (event.wheelDelta > 0) {
        showPreviousImage();
      } else {
        showNextImage();
      }
    };

    document.addEventListener('DOMContentLoaded', () => {
      Array.from(document.querySelectorAll('.galerie-img')).forEach((img) =>
        img.addEventListener('click', handleClickOnImage)
      );

      document
        .querySelector('.lightbox-image')
        .addEventListener('click', closeLightboxAndRemoveImage);
      document
        .querySelector('.lightbox-next')
        .addEventListener('click', showNextImage);
      document
        .querySelector('.lightbox-previous')
        .addEventListener('click', showPreviousImage);
    });
    // **************************************************************************** Vanilla JS End

    return (
      <>
        <h1>Responsive Image Gallery (Vanilla JS in React)</h1>
        <div className="galerie">
          <div ref={this.myRef} className="galerie-img">
            <p>&nbsp;This is a title</p>
            <img src="./images/thumbnails/image_00.jpg" alt="" />
          </div>
          <div ref={this.myRef} className="galerie-img">
            <p>&nbsp;This is a title</p>
            <img src="./images/thumbnails/image_xx.jpg" alt="" />
          </div>
          <div ref={this.myRef} className="galerie-img">
            <p>&nbsp;This is a title</p>
            <img src="./images/thumbnails/image_03.jpg" alt="" />
          </div>
          <div ref={this.myRef} className="galerie-img">
            <p>&nbsp;This is a title</p>
            <img src="./images/thumbnails/image_04.jpg" alt="" />
          </div>
          <div ref={this.myRef} className="galerie-img">
            <p>&nbsp;This is a title</p>
            <img src="./images/thumbnails/image_05.jpg" alt="" />
          </div>
          <div ref={this.myRef} className="galerie-img">
            <p>&nbsp;This is a title</p>
            <img src="./images/thumbnails/image_06.jpg" alt="" />
          </div>
        </div>
      </>
    );
  }
}

export default MyComponent;

It would be great if you could point me into the right direction, so that I can make it work. I also uploaded a Github Repo which inludes a basic Next.js boilerplate and the JS code for the image gallery. In case someone likes to correct it inside the project.

It is a great boilerplate for a simple and responsive image gallery in React and plain JS (when it works) :sweat_smile:

Are you sure you are not attempting to run the code on the server?

Just to give you a bit of context, next will run your code in the server first, parse the result react dom tree, and send it back to the client as pure html with the ability to hydrate the html later.

During this phase, your code won’t have access to browser specific API, like document, window, storage API, location API …etc…etc…

Next give you the possibility of running some code on the client only thanks to dynamic import.
So make sure that your MyComponent is actually rendered only on the client, and not attempted to be parsed in the server.

1 Like

If you are using a boilerplate to avoid having to code it again you might as well use one of the many gallery/carousel components that are on npm or use one from some component library.

If you are using your own code because that has some importance to you, you should refactor it into a React component. You can then reuse that with React when needed.

Right, I think as well as what’s been said, your just taking chunks of code and dumping them into other chunks of code without a lot of thought.

There are hundreds of slideshows and carousels and tutorials on how to do them from scratch: you are really oversomplicating things by trying to avoid using React here. React is used instead of manually manipulating the DOM.

So I’m not sure what Next does w/r/t refs, I never use it, so caveats apply as described by @Marmiz. You’re also using class syntax + shoving everything in the constructor, and I can’t remember if there are any subtleties there I may be forgetting (it’s very bad practice, but I don’t think it’ll do anything too bad)

But putting that aside, ref holds onto some state.

It’s literally this:

{ current: any } | null

ie either an object with one property called current whose value can be anything, or null.

It can be used to hold onto any value you want to persist and work with imperatively between renders. When you give an element a ref attribute, then when the component first actually renders to the DPM, that will set the value of current to be that DOM object. And that reference (“ref”) will persist between rerenders. Once it’s set as that DOM object, then you can now access all the available methods and properties attached to that object.

E.g. if you attached it to a text input, you’d be able to access myInputRef.current.value or apply myInputRef.current.focus()

You’re just not using your ref anywhere. And you’ve added the ref to every single one of those elements: how would that work? The code is going to reach the first element, set current to that DOM element, reach the next one, set current to that DOM element instead and so on.

So like if you had

const galerieRef = createRef();

And you added it to the parent, like

 <div ref={galerieRef} className="galerie">

Then you can use, for example galerieRef.querySelector (that would query the children of that div for a specific element)

1 Like

Hi Friends!

I appologize for the late reply, the last days were bussy. Finally I got the time to create an Image Gallery in Reactjs from scratch and it is really simple doing it the react way from the beginning on. I created a repository, so anyone can use it for there own projects.

Here is the Link to the Repo.

However I am still struggling with the useRef hook, but I will try to get a better understanding about that whenever I find some time or bright moment. I guess its all about server side rendering within Reactjs and Next.js, so as @Marmiz said we just need to render plain JS when the DOM tree and therefore the document is available.

Anyways, thanks for your help!

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.