TypeScript & React: Building a Simple Photo Gallery App

In the digital age, we’re constantly bombarded with images. From social media feeds to personal websites, photos have become an integral part of how we communicate and share experiences. As developers, we often need to display images in a visually appealing and organized manner. This is where a photo gallery application comes in handy. In this tutorial, we’ll dive into building a simple, yet functional, photo gallery using TypeScript and React. We’ll cover everything from setting up our development environment to handling image data and implementing user interactions. By the end of this guide, you’ll have a solid understanding of how to create a dynamic and interactive photo gallery application, and a foundation for more complex image-based projects.

Why TypeScript and React?

Before we jump into the code, let’s discuss why we’re choosing TypeScript and React for this project. TypeScript brings type safety to JavaScript, which helps catch errors early in the development process. This leads to more robust and maintainable code. React, on the other hand, is a popular JavaScript library for building user interfaces. Its component-based architecture makes it easy to create reusable UI elements, and its virtual DOM efficiently updates the user interface, resulting in a smooth user experience. Combining TypeScript and React provides a powerful and efficient way to build modern web applications.

Setting Up the Development Environment

Let’s get our environment ready. We’ll use Create React App to quickly set up a React project with TypeScript support. If you have Node.js and npm (or yarn) installed, open your terminal and run the following command:

npx create-react-app photo-gallery --template typescript

This command creates a new directory called photo-gallery, installs all the necessary dependencies, and sets up a basic React application with TypeScript support. Once the installation is complete, navigate into the project directory:

cd photo-gallery

Now, let’s install a few additional dependencies that we’ll need for our photo gallery:

  • @types/react: TypeScript definitions for React.
  • @types/react-dom: TypeScript definitions for React DOM.
  • react-images: A library for displaying images in a gallery.
  • react-spring: A library for creating animations.
npm install @types/react @types/react-dom react-images react-spring

With our project and dependencies set up, we’re ready to start coding!

Project Structure

Before we start writing code, let’s discuss the project structure. We’ll keep it simple to make it easy to understand. Inside the src directory, we’ll have the following files:

  • App.tsx: The main application component, which will contain the gallery.
  • components/Gallery.tsx: This component will handle displaying the images.
  • models/Image.ts: This file will define the structure of our image data.
  • data/images.ts: This file will contain our image data.
  • styles/Gallery.css: This file will contain the styles for our gallery.

This structure is designed to keep the code organized and easy to maintain.

Defining the Image Model

Let’s start by defining the structure of our image data. Create a file named models/Image.ts and add the following code:

// models/Image.ts

export interface Image {
  id: number;
  src: string;
  alt: string;
  width: number;
  height: number;
}

This interface defines the properties of an image: id, src (the image URL), alt (alternative text for accessibility), width, and height. This structure will help us manage our image data effectively.

Creating the Image Data

Next, let’s create some sample image data. Create a file named data/images.ts and add the following code:

// data/images.ts
import { Image } from '../models/Image';

export const images: Image[] = [
  {
    id: 1,
    src: 'https://placekitten.com/g/600/400',
    alt: 'Kitten 1',
    width: 600,
    height: 400,
  },
  {
    id: 2,
    src: 'https://placekitten.com/g/800/600',
    alt: 'Kitten 2',
    width: 800,
    height: 600,
  },
  {
    id: 3,
    src: 'https://placekitten.com/g/400/600',
    alt: 'Kitten 3',
    width: 400,
    height: 600,
  },
  {
    id: 4,
    src: 'https://placekitten.com/g/700/500',
    alt: 'Kitten 4',
    width: 700,
    height: 500,
  },
];

This file exports an array of Image objects, each with a unique ID, a source URL (using PlaceKitten for example images), alternative text, width, and height. You can replace these with your own image URLs and data.

Building the Gallery Component

Now, let’s create the Gallery component, which will be responsible for displaying the images. Create a file named components/Gallery.tsx and add the following code:

// components/Gallery.tsx
import React, { useState } from 'react';
import { Image } from '../models/Image';
import { useSpring, animated } from 'react-spring';
import 'react-images/lib/components/ReactImages.css';
import './Gallery.css';

interface GalleryProps {
  images: Image[];
}

const Gallery: React.FC = ({ images }) => {
  const [currentImageIndex, setCurrentImageIndex] = useState(null);

  const openLightbox = (index: number) => {
    setCurrentImageIndex(index);
  };

  const closeLightbox = () => {
    setCurrentImageIndex(null);
  };

  const nextImage = () => {
    if (currentImageIndex !== null) {
      setCurrentImageIndex((prevIndex) => (prevIndex + 1) % images.length);
    }
  };

  const prevImage = () => {
    if (currentImageIndex !== null) {
      setCurrentImageIndex((prevIndex) => (prevIndex - 1 + images.length) % images.length);
    }
  };

  const spring = useSpring({
    opacity: currentImageIndex !== null ? 1 : 0,
    transform: `scale(${currentImageIndex !== null ? 1 : 0.8})`,
    config: { tension: 300, friction: 20 },
  });

  return (
    <div>
      <div>
        {images.map((image, index) => (
          <div>
            <img src="{image.src}" alt="{image.alt}"> openLightbox(index)}
            />
          </div>
        ))}
      </div>

      {currentImageIndex !== null && (
        
          <div> e.stopPropagation()}>
            <button>✕</button>
            <button>❮</button>
            <img src="{images[currentImageIndex].src}" alt="{images[currentImageIndex].alt}" />
            <button>❯</button>
          </div>
        
      )}
    </div>
  );
};

export default Gallery;

This component takes an array of Image objects as a prop. It renders a grid of images, each clickable to open a lightbox. When an image is clicked, a lightbox with the full-size image appears. The component also includes navigation controls (next and previous buttons) within the lightbox. The react-spring library is used to create a fade-in and scale-in animation for the lightbox. The component also includes a close button to close the lightbox.

Let’s break down the code:

  • Imports: We import necessary modules, including the Image interface, useSpring and animated from react-spring.
  • Props: The component receives an images prop, which is an array of Image objects.
  • State: currentImageIndex stores the index of the currently displayed image in the lightbox, initialized to null.
  • Functions:
    • openLightbox(index: number): Sets the currentImageIndex to the index of the clicked image, opening the lightbox.
    • closeLightbox(): Sets currentImageIndex to null, closing the lightbox.
    • nextImage(): Increments the currentImageIndex to display the next image, wrapping around to the beginning if necessary.
    • prevImage(): Decrements the currentImageIndex to display the previous image, wrapping around to the end if necessary.
  • Animation: useSpring from react-spring is used to create a smooth animation for the lightbox, controlling its opacity and scale.
  • Rendering:
    • The component renders a grid of images using the images.map() function.
    • Each image is displayed within a div element, and an onClick handler calls openLightbox().
    • If currentImageIndex is not null (i.e., the lightbox is open), an animated overlay is rendered, containing the full-size image, close button, and navigation buttons. The close button and overlay use the same onClick handler to close the lightbox. The navigation buttons use nextImage() and prevImage() to navigate through the images.

Styling the Gallery

To make our gallery look good, let’s add some CSS styles. Create a file named styles/Gallery.css and add the following CSS:

/* styles/Gallery.css */
.gallery-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  font-family: sans-serif;
}

.image-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
  width: 100%;
  max-width: 1200px;
  margin-bottom: 20px;
}

.image-item {
  border: 1px solid #ddd;
  border-radius: 5px;
  overflow: hidden;
  cursor: pointer;
}

.image-item img {
  width: 100%;
  height: auto;
  display: block;
  transition: transform 0.3s ease;
}

.image-item:hover img {
  transform: scale(1.05);
}

.lightbox-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.lightbox-content {
  position: relative;
  background-color: white;
  padding: 20px;
  border-radius: 5px;
  max-width: 90%;
  max-height: 90%;
  overflow: hidden;
}

.lightbox-content img {
  max-width: 100%;
  max-height: 100%;
  display: block;
}

.lightbox-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background-color: transparent;
  border: none;
  font-size: 24px;
  color: white;
  cursor: pointer;
}

.lightbox-prev, .lightbox-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background-color: rgba(0, 0, 0, 0.5);
  border: none;
  color: white;
  font-size: 24px;
  cursor: pointer;
  padding: 10px;
  border-radius: 5px;
}

.lightbox-prev {
  left: 10px;
}

.lightbox-next {
  right: 10px;
}

This CSS provides basic styling for the gallery, including a grid layout for the images, hover effects, and styles for the lightbox overlay and content. The lightbox styles include a semi-transparent background, a close button, and navigation arrows.

Integrating the Gallery into the App

Now, let’s integrate the Gallery component into our main application. Open src/App.tsx and replace its contents with the following code:

// src/App.tsx
import React from 'react';
import Gallery from './components/Gallery';
import { images } from './data/images';

function App() {
  return (
    <div>
      <h1>Photo Gallery</h1>
      
    </div>
  );
}

export default App;

This code imports the Gallery component and the images data from the respective files. It then renders the Gallery component, passing the images data as a prop. Add some basic styling in src/App.css:

/* src/App.css */
.App {
  text-align: center;
  font-family: sans-serif;
}

.App h1 {
  margin-bottom: 20px;
}

Running the Application

Now, it’s time to run our application. In your terminal, make sure you’re in the project directory (photo-gallery) and run the following command:

npm start

This command starts the development server, and your photo gallery application should open in your web browser at http://localhost:3000. You should see a grid of images. Clicking on an image will open it in a lightbox with navigation controls. Congratulations, you’ve successfully built a simple photo gallery application using TypeScript and React!

Common Mistakes and How to Fix Them

While building this application, you might encounter some common mistakes. Here are a few and how to fix them:

  • Type Errors: TypeScript can be strict, and you might see type errors in your console. These errors are helpful because they point out potential issues in your code. Always read the error messages carefully, and make sure your data types match the expected types. For example, if you get an error that says “Type ‘string’ is not assignable to type ‘number’”, you know you’re trying to assign a string value to a variable that expects a number.
  • Incorrect Imports: Make sure you’re importing components and data correctly. Double-check the file paths in your import statements. A common mistake is using the wrong relative path.
  • CSS Issues: If your styles aren’t applied correctly, make sure you’ve imported the CSS file in your component. Check for typos in your CSS class names and ensure your CSS files are correctly linked. Also, make sure that the CSS file is in the correct directory.
  • Lightbox Not Appearing: Ensure that the currentImageIndex state is being updated correctly when you click on an image. Check that the conditional rendering for the lightbox is working as expected. Verify that the CSS for the lightbox is correctly applied.
  • Image URLs Not Working: If your images are not displaying, check the image URLs. Make sure they are valid and accessible from your browser. You can use your browser’s developer tools to check for any network errors. Also, verify that the image paths are correct if you are using local images.
  • Animation Issues: If the animation doesn’t work, verify that you have installed react-spring correctly, and that you have imported and used it properly. Check the console for any errors related to the animation library.

Key Takeaways

  • TypeScript for Type Safety: TypeScript significantly improves code quality and reduces runtime errors. By defining interfaces and types, you can catch errors during development.
  • React for UI Components: React’s component-based architecture makes it easy to build reusable UI elements and manage the application’s state.
  • State Management: Understanding how to manage state (using useState in this example) is crucial for building interactive applications.
  • Component Composition: Breaking down your application into smaller, reusable components (like the Gallery component) makes your code more organized and easier to maintain.
  • CSS Styling: Proper styling is essential for creating a visually appealing user interface.

FAQ

Here are some frequently asked questions about building a photo gallery application:

  1. Can I use different image sources? Yes, you can use images from various sources, such as local files, external URLs, or a database. Just update the src property in your image data to point to the correct image URL.
  2. How can I add image captions? You can add a caption property to your Image interface and display the caption below the image in the lightbox or gallery grid.
  3. How can I add a loading indicator? You can display a loading indicator while the images are loading. Use the onLoad event on the img tag to track when the image has finished loading.
  4. How can I implement image filtering and sorting? You can add filters and sorting options to your gallery by creating a state variable to hold the filter and sort criteria. Then, you can filter and sort the image data based on these criteria and re-render the gallery.
  5. How can I make the gallery responsive? Use CSS media queries to adjust the gallery’s layout based on the screen size. For example, you can change the number of columns in the image grid for different screen sizes.

Building a photo gallery application is a great way to learn about TypeScript, React, and how to create interactive web applications. By following this tutorial, you’ve gained practical experience in setting up a development environment, defining data models, building components, handling user interactions, and styling your application. With the knowledge and skills you’ve gained, you’re well-equipped to tackle more complex web development projects. Remember that the key to mastering any new technology is practice. Experiment with the code, add new features, and try different approaches to deepen your understanding. Happy coding!