Building a Stunning Gallery App with React, Firebase, Unsplash API, and Tailwind CSS

Welcome to this step-by-step guide on building a beautiful gallery app using popular technologies like React, Firebase, the Unsplash API, and Tailwind CSS. In this tutorial, we'll walk you through the entire process, from project setup to the creation of key components. By the end, you'll have a working gallery app that you can use as a foundation for your own projects.

Technologies Used

Before we dive into the code, let's briefly introduce the technologies we'll be using:

  • React: A popular JavaScript library for building user interfaces.

  • Firebase: A comprehensive platform for building web and mobile applications with features like authentication.

  • Unsplash API: A free and reliable source of high-quality images for developers.

  • Tailwind CSS: A utility-first CSS framework for rapid UI development.

Project Setup

To get started, we'll set up our project using Create React App and Firebase. Make sure you have Node.js installed, then run the following commands:

npx create-react-app gallery-app
cd gallery-app
npm install firebase

Once you've created your Firebase project, replace the Firebase configuration in your firebase.js file with your own project's configuration.

Now, let's explore the core components of our gallery app: LoginPage and GalleryPage.

LoginPage Component

Explanation

The LoginPage component handles user authentication. It uses Firebase's signInWithEmailAndPassword function to authenticate users with their email and password. If authentication fails, it displays an error message.

import React, { useState } from 'react';
import { auth } from "../firebase";
import { signInWithEmailAndPassword } from "firebase/auth";
import { Link, useNavigate } from "react-router-dom";

const LoginPage = () => {
    const navigate = useNavigate();
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [notice, setNotice] = useState("");

    const loginWithUsernameAndPassword = async (e) => {
        e.preventDefault();

        try {
            await signInWithEmailAndPassword(auth, email, password);
            navigate("./gallery");
        } catch {
            setNotice("You entered the wrong username or password.");
        }
    }

    return (
        // JSX for the login form
        // ...
    );
};

Code Explanation

In this component:

  • We import the necessary functions and components.

  • We use React hooks to manage the state of email, password, and notice.

  • The loginWithUsernameAndPassword function is called when the "Sign in" button is clicked. It attempts to sign in the user with the provided email and password using Firebase's signInWithEmailAndPassword function.

  • If authentication fails, an error message is displayed.

GalleryPage Component

Explanation

The GalleryPage component displays a gallery of images fetched from the Unsplash API. It also includes a search bar for filtering images by keywords.

import React, { useState, useEffect } from 'react';
import { auth } from "../firebase";
import { onAuthStateChanged } from "firebase/auth";
import { Link, useNavigate } from "react-router-dom";

const GalleryPage = () => {
    const navigate = useNavigate();
    const [searchResults, setSearchResults] = useState([]);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);

    useEffect(() => {
        // Check if the user is authenticated
        const unsubscribe = onAuthStateChanged(auth, (user) => {
            if (!user) {
                // User is not authenticated, redirect to the login page
                navigate('/');
            }
        });

        // Clean up the listener when the component unmounts
        return () => unsubscribe();
    }, [navigate]);

    const handleSearch = (query) => {
        setError(null);
        setLoading(true);

        // Perform the API request here and update searchResults
        fetchImagesFromUnsplash(query);
    };

    async function fetchImagesFromUnsplash(query) {
        try {
            const accessKey = "ASce65Gi4cay9Tk_RglFvqOlflyKG8MX9rc3erBpCRc";
            // Make the API request and set searchResults with the fetched images
            const response = await fetch(
                `https://api.unsplash.com/search/photos?query=${query}&per_page=16`,
                {
                    headers: {
                        Authorization: `Client-ID ${accessKey}`,
                    },
                }
            );

            if (!response.ok) {
                throw new Error('Network response was not ok');
            }

            const data = await response.json();
            setSearchResults(data.results);
            setLoading(false);
        } catch (error) {
            setError('An error occurred while fetching images from Unsplash. Please try again later.');
            setLoading(false);
        }
    }

    return (
        // JSX for the GalleryPage component
        // ...
    );
};

Code Explanation

In this component:

  • We use React hooks to manage the state of searchResults, loading, error, and to check if the user is authenticated.

  • The handleSearch function is called when a search query is submitted. It initiates an API request to Unsplash to fetch images based on the query.

  • The fetchImagesFromUnsplash function sends a request to the Unsplash API using the provided query and updates the searchResults state with the fetched images.

App.js Configuration

Explanation

In App.js, we configure routing using React Router, allowing us to switch between the LoginPage and GalleryPage components.

import React from 'react';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import LoginPage from "./pages/LoginPage";
import GalleryPage from "./pages/GalleryPage";
import Layout from "./Layout";

const App = () => {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={<Layout />}>
                    <Route index element={<LoginPage />} />
                    <Route path="/gallery" element={<GalleryPage />} />
                </Route>
            </Routes>
        </BrowserRouter>
    );
};

Code Explanation

In this configuration:

  • We use the BrowserRouter component to enable client-side routing.

  • We define routes for the root path, which renders the Layout component, and nested routes for the LoginPage and GalleryPage components.

Firebase Configuration

Explanation

The firebase.js file initializes Firebase and provides access to authentication services using the Firebase configuration details from your Firebase project.

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
    // Your Firebase configuration details here
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

export { auth };

Code Explanation

In this configuration:

  • We import the necessary functions from Firebase.

  • We initialize Firebase using the provided firebaseConfig.

  • We create an authentication object (auth) that provides access to Firebase authentication services.

Explanation

The Gallery component is responsible for displaying images and allowing users to reorder them via drag-and-drop. It also handles fetching images from the Unsplash API.

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

const Gallery = ({ searchResults }) => {
    const [images, setImages] = useState([]);
    const [draggedImage, setDraggedImage] = useState(null);

    useEffect(() => {
        const apiUrl = 'https://api.unsplash.com/photos';
        const accessKey = "##########_#############################"; // Replace with your actual access key
        const perPage = 16;

        if (!searchResults || searchResults.length === 0) {
            // Fetch the first 16 pictures from the API by default
            fetch(`${apiUrl}?per_page=${perPage}`, {
                headers: {
                    Authorization: `Client-ID ${accessKey}`,
                },
            })
                .then((response) => {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then((data) => {
                    const imageUrls = data.map((photo) => photo.urls.regular);
                    setImages(imageUrls);
                })
                .catch((error) => {
                    console.error('Error fetching images from Unsplash:', error);
                });
        } else {
            // If there are search results, update the images with the search results
            const searchImageUrls = searchResults.map((photo) => photo.urls.regular);
            setImages(searchImageUrls);
        }
    }, [searchResults]);

    // ...
    // Rest of the Gallery component code for drag-and-drop functionality

    return (
        // JSX for the Gallery component
        // ...
    );
};

Code Explanation

In this component:

  • We use React hooks to manage the state of images and draggedImage.

  • In the useEffect hook, we fetch images from the Unsplash API using the provided access key. If there are no search results, it fetches the first 16 pictures from the API.

  • The handleDragStart, handleDragEnd, handleDragOver, and handleDrop functions enable drag-and-drop functionality for reordering images.

  • The component maps and renders the fetched images, allowing users to interact with them.

Conclusion

Congratulations! You've successfully built a stunning gallery app with React, Firebase, the Unsplash API, and Tailwind CSS. We've covered key components, authentication, image fetching, and more. Feel free to explore and customize this project further for your needs.

Additional Resources


That concludes our tutorial on building a gallery app with React, Firebase, the Unsplash API, and Tailwind CSS. We hope you found this guide helpful in starting your own projects. Happy coding!

Hosted Project

You can explore the hosted version of this project here.

The complete source code for this project can be found on GitHub.

Note: The default login credentials to access the gallery on the hosted link are as follows:


We hope you enjoy building and customizing your gallery app. If you have any feedback or questions, please don't hesitate to reach out. Happy coding!

0
Subscribe to my newsletter

Read articles from Agbeniga Agboola directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Agbeniga Agboola
Agbeniga Agboola