React / 5 MIN READ
Gallery with Grid
Picture this.
From the original Fervor library. Examples may use older package versions.
React Picture Gallery Grid with Custom Modal
We will create a picture gallery grid, where each picture is clickable and opens in a custom modal with a fade transition. The modal will also have a caption for each picture and arrows for navigating between pictures.
Step 1: Setting up the project
Create a new React app using Create React App:
npx create-react-app gallery-modal
This will set up a new React project in a folder called gallery-modal.
Step 2: Creating the Picture Gallery
Let’s create a Gallery component that displays a list of pictures in a grid. Each picture is clickable and triggers an event that will be used to open the modal.
In src/components/Gallery.js:
import React from 'react';
import './Gallery.css';
const Gallery = ({ images, onImageClick }) => {
return (
<div className="gallery">
{images.map((image) => (
<div className="gallery-item" key={image.id} onClick={() => onImageClick(image)}>
<img src={image.url} alt="" />
<p>{image.caption}</p>
</div>
))}
</div>
);
};
export default Gallery;
And the corresponding CSS in src/components/Gallery.css:
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 1em;
}
.gallery-item {
cursor: pointer;
}
Step 3: Creating the Custom Modal
Next, we will create a custom GalleryModal component. When an image is clicked in the Gallery, the GalleryModal will open with a fade-in transition.
In src/components/GalleryModal.js:
import React from 'react';
import './GalleryModal.css';
const GalleryModal = ({ isOpen, onRequestClose, image, prevImage, nextImage }) => {
if (!isOpen) {
return null;
}
return (
<div className="modal-overlay" onClick={onRequestClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<img src={image.url} alt="" />
<p>{image.caption}</p>
<button onClick={prevImage}><</button>
<button onClick={onRequestClose}>close</button>
<button onClick={nextImage}>></button>
</div>
</div>
);
};
export default GalleryModal;
And the corresponding CSS in src/components/GalleryModal.css:
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
animation: showOverlay 0.3s forwards;
}
@keyframes showOverlay {
to {
opacity: 1;
}
}
.modal-content {
background-color: white;
padding: 1em;
max-width: 500px;
max-height: 80vh;
overflow: auto;
position: relative;
z-index: 1001;
opacity: 0;
animation: showModalContent 0.3s forwards;
}
@keyframes showModalContent {
to {
opacity: 1;
}
}
Step 4: Implementing the App Component
Now, let’s put it all together in the App component. The App component will fetch the images, manage the modal state (which image is currently displayed, whether the modal is open), and handle the image navigation.
First, replace App.js with the following:
import React, { useState, useEffect } from 'react';
import Gallery from './components/Gallery';
import GalleryModal from './components/GalleryModal';
function App() {
const [images, setImages] = useState([]);
const [currentImage, setCurrentImage] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
fetch('images.json')
.then((response) => response.json())
.then(setImages);
}, []);
const openModal = (image) => {
setCurrentImage(image);
setIsModalOpen(true);
};
const closeModal = () => {
setCurrentImage(null);
setIsModalOpen(false);
};
const prevImage = () => {
const currentIndex = images.findIndex((image) => image.id === currentImage.id);
if (currentIndex > 0) {
setCurrentImage(images[currentIndex - 1]);
}
};
const nextImage = () => {
const currentIndex = images.findIndex((image) => image.id === currentImage.id);
if (currentIndex < images.length - 1) {
setCurrentImage(images[currentIndex + 1]);
}
};
return (
<div className="App">
<Gallery images={images} onImageClick={openModal} />
{currentImage && (
<GalleryModal
isOpen={isModalOpen}
onRequestClose={closeModal}
image={currentImage}
prevImage={prevImage}
nextImage={nextImage}
/>
)}
</div>
);
}
export default App;
This App component fetches the images from a JSON file in the public folder on mount and saves them in the state. When an image is clicked in the Gallery, it opens the GalleryModal with the clicked image. The prevImage and nextImage functions allow the user to navigate through the images while the modal is open.
Replace 'images.json' with the path to your JSON file, and make sure the JSON file is correctly formatted, like the example you provided.
Step 5: Running the Application
To run the application, use the command:
npm start
Your application is now running and can be accessed at http://localhost:3000. When you click on an image, a modal with the image, its caption, and navigation buttons should appear. The modal should have a fade-in transition and can be closed by either clicking on the ‘close’ button or outside the modal.
Conclusion
Congratulations! You’ve created a picture gallery with a custom modal in React. By using state and effect hooks, you’ve built an interactive application that fetches data, displays it in a responsive grid, and provides a detailed view of each image with navigation. This is a strong foundation for more complex gallery applications and demonstrates key concepts in React development.
Bonus animations for gallery
For a fade-in and fade-out transition between images when the next and previous buttons are clicked, we can use CSS animations. However, since React re-renders components when the state changes, it’s tricky to maintain the fading out state.
The following approach provides a solution using a key on the image element and using CSS animations:
Update the GalleryModal component in src/components/GalleryModal.js as follows:
import React from 'react';
import './GalleryModal.css';
const GalleryModal = ({ isOpen, onRequestClose, image, prevImage, nextImage }) => {
if (!isOpen) {
return null;
}
return (
<div className="modal-overlay" onClick={onRequestClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<img className="image-transition" key={image.id} src={image.url} alt="" />
<p>{image.caption}</p>
<button onClick={prevImage}><</button>
<button onClick={onRequestClose}>close</button>
<button onClick={nextImage}>></button>
</div>
</div>
);
};
export default GalleryModal;
Next, let’s modify GalleryModal.css to include the fade-in and fade-out animations:
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
animation: showOverlay 0.3s forwards;
}
@keyframes showOverlay {
to {
opacity: 1;
}
}
.modal-content {
background-color: white;
padding: 1em;
max-width: 500px;
max-height: 80vh;
overflow: auto;
position: relative;
z-index: 1001;
opacity: 0;
animation: showModalContent 0.3s forwards;
}
@keyframes showModalContent {
to {
opacity: 1;
}
}
.image-transition {
opacity: 0;
animation: fadeInOut 1s forwards;
}
@keyframes fadeInOut {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
This adds a key to the image element, which will force React to re-render the image element whenever the key changes (i.e., when the image changes), and adds a new CSS animation for the image fade-in and fade-out transition. Now, when you click the next or previous buttons, the image will fade out and the new image will fade in.