<!-- https://blog.alexdevero.com/learn-react-practice-create-gallery/ -->
<h1>Image Gallery</h1>
<section class="react-gallery"></section>
// Variables
$black: #111;
$radius: 10px;
$transition: all .25s ease-in-out;

html,
body {
  min-height: 100%;
  height: 100%;
}

html {
  font-size: 16px;
}

body {
  position: relative;
  font-size: 100%;
}

.gallery-container {
  padding-top: .9375rem;
}

.gallery-card {
  position: relative;
  overflow: hidden;
  margin-bottom: 1.875rem;
}

.gallery-thumbnail {
  max-width: 100%;
  height: auto;
  border-radius: $radius;
}

.card-icon-open {
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  font-size: 2rem;
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transform: translate(-50%, -50%);
  transition: $transition;
  
  &:focus,
  &:hover {
    color: $black;
  }
}

.gallery-thumbnail:focus ~ .card-icon-open,
.gallery-thumbnail:hover ~ .card-icon-open,
.gallery-thumbnail ~ .card-icon-open:focus,
.gallery-thumbnail ~ .card-icon-open:hover {
  opacity: 1;
}

.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 10;
  width: 100%;
  height: 100%;
  background: rgba(21,21,21,.75);
}

.modal-body {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 11;
  padding: 0;
  overflow: auto;
  max-width: 100%;
  max-height: 100%;
  border-radius: $radius;
  transform: translate(-50%, -50%);
}

.modal-close {
  position: absolute;
  top: 0;
  right: 8px;
  font-size: 1.5rem;
  color: $black;
  transition: $transition;
  
  &:focus,
  &:hover {
    color: #fff;
  }
}
View Compiled
// Cache gallery container
const galleryContainer = document.querySelector('.react-gallery');

// Create new array with URLs for images
let imgUrls = [
  'https://source.unsplash.com/4rDCa5hBlCs/800x600',
  'https://source.unsplash.com/01vFmYAOqQ0/800x600',
  'https://source.unsplash.com/2Bjq3A7rGn4/800x600',
  'https://source.unsplash.com/cFplR9ZGnAk/800x600',
  'https://source.unsplash.com/pHANr-CpbYM/800x600',
  'https://source.unsplash.com/RkmyeYA6xuE/800x600',
  'https://source.unsplash.com/E4944K_4SvI/800x600',
  'https://source.unsplash.com/-hI5dX2ObAs/800x600',
  'https://source.unsplash.com/vZlTg_McCDo/800x600'
];

// Component for gallery image
class GalleryImage extends React.Component {
  render() {
    return(
      <img className={this.props.className} src={this.props.src} alt={this.props.alt} />
    )
  }
}

// Component for gallery modal
class GalleryModal extends React.Component {
  render() {
    if (this.props.isOpen === false) {
      return null;
    }
    
    return(
      <div isOpen={this.props.isOpen} className='modal-overlay' onClick={this.props.onClick} name={this.props.name}>
        <div className='modal-body'>
          <a className='modal-close' href='#' onClick={this.props.onClick}><span className='fa fa-times'></span></a>
          
          <img src={this.props.src} />
        </div>
      </div>
    )
  }
}

// Component for gallery
class Gallery extends React.Component{
  constructor(props) {
    super(props);
    
    this.state = {
      showModal: false,
      url: ''
    }
    
    this.openModal = this.openModal.bind(this);
    
    this.closeModal = this.closeModal.bind(this);
  }
  
  render() {
    return(
      <div refs='gallery-container' className='container-fluid gallery-container'>
        <div className='row'>
          {
            imgUrls.map((url, index) => {
               return <div className='col-sm-6 col-md-3 col-xl-2'>
                  <div className='gallery-card'>
                    <GalleryImage className='gallery-thumbnail' src={url} alt={'Image number ' + (index + 1)} />
                    
                    <span className='card-icon-open fa fa-expand' value={url} onClick={(e) => this.openModal(url, e)}></span>
                  </div>
                </div>
             })
           }
        </div>
        
        <GalleryModal isOpen={this.state.showModal} onClick={this.closeModal} src={this.state.url} /> 
      </div>
    )
  }
  
  // Function for opening modal dialog
  openModal(url, e) {
     this.setState({
       showModal: true,
       url: url
     })
   };

  // Function for closing modal dialog
  closeModal() {
    this.setState({
      showModal: false,
      url: ''
    })
  }
}

// Let's render the whole thing
ReactDOM.render(
  <Gallery imgUrls={imgUrls} />
, galleryContainer);
View Compiled
Run Pen

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css
  2. https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.min.js