dark mode

Create React wrapper for PhotoSwipe

Create React wrapper for PhotoSwipe

Everyone has a favourite library, but there are chances that has no version for the framework you are using or it’s an old port with issues.

PhotoSwipe is an image gallery, the library wrapper for react has unnecessary dependencies, deprecated life cycles and issues that needed the creation of react-photoswipe-2.

With these problems it's unwise to depend of something that is no longer supported.

This is how to create React wrapper for PhotoSwipe, but the basic principles are the same for all other libraries. You need to import the package and expose its APIs.

First, we need to import the library and bind it with React. We will create a component in a file called photoSwipe.js. It will be a functional component, but you can change it in a class component with an ease. The imports will be:

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

import '../../node_modules/photoswipe/dist/photoswipe.css';
import '../../node_modules/photoswipe/dist/default-skin/default-skin.css';
import PhotoSwipe from '../../node_modules/photoswipe/dist/photoswipe.js';
import PhotoSwipeUI_Default from '../../node_modules/photoswipe/dist/photoswipe-ui-default.js';

The library want us to include two CSS and two JavaScript files. We know that the CSS rules will apply globally when the page is loading, but how to access the JavaScript file? We look the JS file as a module and we need a name to access it later this JS scope.

In our case we bind the two files with the React component with the names PhotoSwipe and PhotoSwipeUI_Default.

Second step is to add all needed DOM elements. We do that in the return. We need to convert the html to jsx so change everywhere class to className, tabindex to tabIndex and aria-hidden to ariaHidden.

Next step is to initialize PhotoSwipe constructor:

const photoSwipe = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, props.items, options);

The constructor has 4 params:

  1. pswpElement is the first <div> in the return, the one with the className="pswp". To have reference we will use useRef Hook. We initialize the variable let pswpElement = useRef(null); and then add the ref in the <div> ref={node => { pswpElement = node; }}.
  2. PhotoSwipeUI_Default is the import that we already have;
  3. props.items this is the data array from which we will build all the slides:

    // build items array
    const items = [
     {
       src: 'https://placekitten.com/600/400',
       w: 600,
       h: 400
     },
     {
       src: 'https://placekitten.com/1200/900',
       w: 1200,
       h: 900
     }
    ];
  4. options is the object where we define options:

    // define options (if needed)
    const options = {
     // optionName: 'option value'
     // for example:
     index: 0 // start at first slide
    };

The PhotoSwipe constructor should init only once when the component is loading and we must bind events that listen when we open and close the gallery with React. All of this should be in the useEffect Hook.

In the final code below we will see the code we need in the useEffect Hook. We notice that useEffect Hook has an array as a second argument. This means that the Hook will execute every time when props or gallery options are changing.

Now we can change props in the parent component. For example, we can notify parent component that the gallery is closed when listening for PhotoSwipe's destroy and close events. Also, with props we can know when to open the gallery, because changing props from the parent will fire useEffect Hook. This is the general way to wire events and life-cycles.

Our main component should have thumbnails, galleries, images' data and methods to open and close PhotoSwipe.

Image data for PhotoSwipe should be an array of objects.

The methods for open and close as you can see in the final code below are just simple functions that change state and pass it through props to the wrapper. The only difference is that when we interact with the grid of thumbnails we need to open different image. To do that, we add another property to the state which is the index.

Creating a wrapper in React is simple enough when we understand the way wrappers and our project communicated and how to wire events and life-cycles. In this guide we did all of this and is the foundation to create and extend React wrappers.

Final code for the wrapper

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

import '../../node_modules/photoswipe/dist/photoswipe.css';
import '../../node_modules/photoswipe/dist/default-skin/default-skin.css';
import PhotoSwipe from '../../node_modules/photoswipe/dist/photoswipe.js';
import PhotoSwipeUI_Default from '../../node_modules/photoswipe/dist/photoswipe-ui-default.js';

const PhotoSwipeWrapper = props => {
  let pswpElement = useRef(null);

  const options = {
    index: props.index || 0,
    closeOnScroll: false,
    history: false
  };

  useEffect(() => {
    const photoSwipe = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, props.items, options);

    if (photoSwipe) {
      if (props.isOpen) {
        photoSwipe.init();

        photoSwipe.listen('destroy', () => {
          props.onClose();
        });

        photoSwipe.listen('close', () => {
          props.onClose();
        });
      }
      if (!props.isOpen) {
        props.onClose();
      }
    }
  }, [props, options]);

  return (
    <div
      className="pswp"
      tabIndex="-1"
      role="dialog"
      aria-hidden="true"
      ref={node => {
        pswpElement = node;
      }}
    >
      <div className="pswp__bg" />
      <div className="pswp__scroll-wrap">
        <div className="pswp__container">
          <div className="pswp__item" />
          <div className="pswp__item" />
          <div className="pswp__item" />
        </div>
        <div className="pswp__ui pswp__ui--hidden">
          <div className="pswp__top-bar">
            <div className="pswp__counter" />
            <button className="pswp__button pswp__button--close" title="Close (Esc)" />
            <button className="pswp__button pswp__button--share" title="Share" />
            <button className="pswp__button pswp__button--fs" title="Toggle fullscreen" />
            <button className="pswp__button pswp__button--zoom" title="Zoom in/out" />
            <div className="pswp__preloader">
              <div className="pswp__preloader__icn">
                <div className="pswp__preloader__cut">
                  <div className="pswp__preloader__donut" />
                </div>
              </div>
            </div>
          </div>
          <div className="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
            <div className="pswp__share-tooltip" />
          </div>
          <button className="pswp__button pswp__button--arrow--left" title="Previous (arrow left)" />
          <button className="pswp__button pswp__button--arrow--right" title="Next (arrow right)" />
          <div className="pswp__caption">
            <div className="pswp__caption__center" />
          </div>
        </div>
      </div>
    </div>
  );
};

export default PhotoSwipeWrapper;

Final code for our gallery

import React, { useState, Fragment } from 'react';
import PhotoSwipeWrapper from './photoSwipe';

export default () => {
  const [isOpen, setIsOpen] = useState(false);
  const [index, setIndex] = useState(0);

  const items = [
    {
      src: 'https://placekitten.com/600/400',
      w: 600,
      h: 400
    },
    {
      src: 'https://placekitten.com/1200/900',
      w: 1200,
      h: 900
    }
  ];

  const handleOpen = index => {
    setIsOpen(true);
    setIndex(index);
  };

  const handleClose = () => {
    setIsOpen(false);
  };

  return (
    <Fragment>
      <div>
        {items.map((item, i) => (
          <div
            key={i}
            onClick={() => {
              handleOpen(i);
            }}
          >
            Image {i}
          </div>
        ))}
      </div>
      <PhotoSwipeWrapper isOpen={isOpen} index={index} items={items.large} onClose={handleClose} />
    </Fragment>
  );
};

Related articles

© 2019 All rights reserved.