How To Build a Photo Search App with React Using the Unsplash API
How To Build a Photo Search App with React Using the Unsplash API

Tutorial

How To Build a Photo Search App with React Using the Unsplash API

APIDevelopmentJavaScriptReact

The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

Introduction

According to the StackOverflow 2020 Developer Survey, React is one of the most popular JavaScript frameworks, and there are many reasons for this, like efficiently changing web app views with the Virtual DOM, using reusable, composable, and stateful components to increase scalability, and more. Beginner React developers often need experience putting their knowledge to use in real-life applications. This tutorial will give you that experience by showing you how to use React Hooks, use useState(), and make API calls in React.

This article will discuss the step-by-step process of building a photo search application with React using the Unsplash API. Unsplash is currently one of the most used and popular photo search engines, and can be a great data provider when building projects and applications.

At the end of this tutorial, you’ll have a working application that uses React Hooks to query the Unsplash API. This project can also act as a boilerplate, since you can re-use the same programming logic and can use it as a base to build other projects involving API calls. Your photo search application will include a search bar and rendered results, as shown in the following:

Photo Search Application

If you would like to see the complete code, take a look at the DigitalOcean Community GitHub Repository.

Prerequisites

In order to follow this guide:

Step 1 — Creating an Empty Project

In this step, you will make use of Create React App, which will get the initial project running without doing any manual configuration. In your project directory, run the following command.

  • npx create-react-app react-photo-search

This command will create a folder named react-photo-search with all the necessary files and configuration for a working React web aplication.

Use the cd command to change directory and go inside this folder by running the following command:

  • cd react-photo-search

Next, start the development server by running the following command:

  • npm start

For information on this start script, check out How To Set Up a React Project with Create React App.

Next, head over to http://localhost:3000 in a web browser, or if you are running this from a remote server, http://your_domain:3000.

You will find the React template:

React starting template with React logo

Before moving further, you will have to clean the files. Create React App comes with sample code that is not needed and should be removed before building a project to ensure code maintainability.

You will now need to open another terminal since one is already taken up by npm start.

Delete the default styling in index.css by running the following command:

  • rm src/index.css

Next, open index.js in a code editor with the following command:

  • nano src/index.js

Since you have deleted index.css, remove import './index.css'; from index.js.

Your index.js will be similar to this once you are done removing import ./index.css from it.

react-photo-search/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Save and exit the file.

Now delete the React logo by running the following command in the terminal:

  • rm src/logo.svg

Open App.css with the following command:

  • nano src/App.css

Remove everything from App.css, then save and exit the file. You will update this in Step 3 with your new styling.

Open src/App.js with the following command:

  • nano src/App.js

The next step is to remove import logo from './logo.svg'; and remove the JSX from the div with the className="App" in App.js file. This will remove the HTML elements of the template.

Modify App.js to look like this:

react-photo-search/src/App.js
import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">

    </div>
  );
}

export default App;

Your http://localhost:3000 will be blank now.

You’ve now initialized a React app and cleaned the sample code from it. Next, you will create a new application in the Unsplash Developer dashboard and copy the Access Key and Secret Key of the application you just created to gain access to the Unsplash API.

Step 2 — Acquiring Unsplash API Credentials

In this section, you will apply for an Unsplash Developer Account, create a new application for this project, and copy the Access Key and Secret Key of this application to gain access to the Unsplash API. Since the Unsplash API is not a public API, you will need your own set of Unsplash API keys for this project.

Head over to Unsplash Developer Home and register as a developer. Since you already created an Unsplash Account this will be a quick process.

On the Unsplash Developer page, click the Register as a developer button.

Unsplash Developer page

Fill in your credentials to register.

After registering as a developer, you will be automatically redirected to your developer dashboard. Click on New Application.

Unsplash Developer Dashboard with New Application

You will be asked to accept the API Use and Guidelines. Click the checkboxes then the Accept terms button to proceed further:

Unsplash API Use and Guidelines

You will then be prompted to give your Application information. Give your application an appropriate name and description, and click Create application.

Unsplash Application Information Pop-up

With this, you have created an application and can now access your Access Key and Secret Key under the Keys section. Copy these keys to a secure location; you will need them later in your code.

Keys section of the Unsplash Application Page

Note that you will see a Demo tag after your application name:

Demottag Next To Unsplash Application Name

This tag means your application is in development mode and the requests are limited to 50 per hour. For a personal project, this is more than enough, but you can also apply for production which will increase the requests limit to 5000 per hour. Do remember to follow the API Guidelines before applying.

In this section, you created an Unsplash API application and acquired the keys required for this project. For this project, you will use the official Unsplash JavaScript Library, unsplash-js, to integrate the API with your app. You will install unsplash.js and add CSS to style your project in the next step.

Step 3 — Installing Dependencies and Adding CSS

You will now install the unsplash-js package as a dependency and add custom CSS to style your project. If at any point you get stuck, refer to the DigitalOcean Community Repository for this project.

To install unsplash-js library with the npm package manager, run the following in your project directory:

  • npm install unsplash-js

This is the only library that you will need to install to follow this tutorial; later on, you can experiment with different React User Interface libraries like React-Bootstrap, Semantic UI React, etc. You should add these libraries if, after following this tutorial, you want to tweak this project and change its layout.

Next, you will style your React app. Open App.css by running the following command.

  • nano src/App.css

This tutorial will discuss the CSS piece by piece.

First is the * selector, which selects all the elements. Add the following code:

react-photo-search/src/App.css
* {
  box-sizing: border-box;
  background-color: rgb(244, 244, 244);
  color: #333;
  font-size: 10px;
}

The box-sizing property sets how the total width and height of an element is calculated and, in this case, it tells the browser to take border and padding into the calculation for an element’s width and height. The background color is set using background-color and the value is rgb(244, 244, 244), which gives a pale white color to the background. color sets the color of the text of the elements; here hexcode #333 is used, which is a dark shade of gray. font-size sets the size of the font.

Next, add the .App block, which selects the element with the className="App". By default the parent element (className="App") has some margin and padding, so the following code sets margin and padding of all four sides to 0:

react-photo-search/src/App.css
* {
  box-sizing: border-box;
  background-color: rgb(244, 244, 244);
  color: #333;
  font-size: 10px;
}

.App {
  margin: 0;
  padding: 0;
}

Next, add styling to the div element with the className="container". This is the child element of the div with className="App". Everything including title, form, button, and images will be included in this div:

react-photo-search/src/App.css
* {
  box-sizing: border-box;
  background-color: rgb(244, 244, 244);
  color: #333;
  font-size: 10px;
}

.App {
  margin: 0;
  padding: 0;
}

.container {
  margin: 0 auto;
  max-width: 1000px;
  padding: 40px;
}

The margin property is used to defined space around elements. margin can be set for top, right, bottom, and left. If only one value is added, then this one value will set for all top, right, bottom, and left. If two values are added in margin, then the first value will be set for top and bottom, and the second will be set for right and left.

According to margin: 0 auto;, top and bottom have 0 margins while left and right have auto. This auto means that the browser will set the margin based on the container. An example to understand this will be if the parent element is 100px and the child element is 50px, then the left, and right margins will be 25px, which will center the child element inside the parent element.

max-width sets the maximum value of width of the element, which in this case is 1000px. If the content is larger than 1000px, then the height property of the element will change accordingly, else max-width will have no effect.

As discussed above, margin sets the space around the element while padding sets the space between an element and its content. The earlier code means that the container div and the elements inside it will have 40px of space between them from all four sides.

Next, add styling for the title of the application:

react-photo-search/src/App.css
...
.container {
  margin: 0 auto;
  max-width: 1000px;
  padding: 40px;
}

.title {
  font-size: 4.4rem;
  font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
}

.title corresponds to the title of your App, which is “React Photo Search”. Only two properties are set, which are font-size and font-family. Here, the rem unit is used for the font-size value. rem values are relative to the root html element, unlike em values, which are relative to the parent element. Here the 4.4rem means 44px (4.4 x 10). This multiplication by 10px is because you set the font size of all elements to 10px using * selector. font-family specifies the font of the element. There are many values passed in the code to act as a fallback system; if the browser does not provide the first font, the next font is set.

Next is the .form CSS block, which includes the form that will be used to search for images. This includes the input search field, button, and label.

react-photo-search/src/App.css
...
.title {
  font-size: 4.4rem;
  font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
}

.form {
  display: grid;
}

Here, only the display property is set. This property specifies the display behavior of the element. It can take different values like grid, flex, block, inline, etc. grid displays an element as a block-level and renders the content according to the grid model.

Next is the .label and the .input CSS block:

react-photo-search/src/App.css
...
.form {
  display: grid;
}

.label {
  font-size: 3rem;
  margin-bottom: 1rem;
}

.input {
  font-size: 1.6rem;
  padding: 0.5rem 2rem;
  line-height: 2.8rem;
  border-radius: 20px;
  background-color: white;
  margin-bottom: 1rem;
}

We have already discussed font-size, padding, background-color, and margin-bottom, so let’s discuss line-height and border-radius. border-radius defines the radius of the element’s corners. Here the value is set to 20px, which will be used for all the four sides. Setting border-radius to 50% can make a square element into an oval. line-height specified the height of the line, which is set to 2.8rem or 28px.

Next is the .button CSS block, which styles the Search button:

react-photo-search/src/App.css
...
.input {
  font-size: 1.6rem;
  padding: 0.5rem 2rem;
  line-height: 2.8rem;
  border-radius: 20px;
  background-color: white;
  margin-bottom: 1rem;
}

.button {
  background-color: rgba(0, 0, 0, 0.75);
  color: white;
  padding: 1rem 2rem;
  border: 1px solid rgba(0, 0, 0, 0.75);
  border-radius: 20px;
  font-size: 1.4rem;
  cursor: pointer;
  transition: background-color 250ms;
}

We have already discussed background-color, color, padding, border-radius, and font-size. border sets the style, width, and color of the border of an element. Here border is used as a shorthand property for border-width, border-style, and border-color. This code adds a solid black color border of 1px around the Search button. cursor specifies the mouse cursor when pointing over an element.

Next is the :hover selector, which is used on .button.

react-photo-search/src/App.css
...
.button {
  background-color: rgba(0, 0, 0, 0.75);
  color: white;
  padding: 1rem 2rem;
  border: 1px solid rgba(0, 0, 0, 0.75);
  border-radius: 20px;
  font-size: 1.4rem;
  cursor: pointer;
  transition: background-color 250ms;
}

.button:hover {
  background-color: rgba(0, 0, 0, 0.85);
}

This means that when the mouse is hovered over the .button element, the background color will change.

The next CSS block is .card-list, which corresponds to the div with className="card-list". This div will display all the images inside it:

react-photo-search/src/App.css
...
.button:hover {
  background-color: rgba(0, 0, 0, 0.85);
}

.card-list {
  column-count: 3;
}

column-count divides the element into columns according to the value that is passed inside it. This code will divide the card-list div into three columns, and the images will be displayed within these three columns.

Next are the .card and .card--image CSS blocks. .card refers to the individual div with an image inside it, and .card--image is the className of this image:

react-photo-search/src/App.css
...
.card-list {
  column-count: 3;
}

.card {
    margin-bottom: 1rem;
    display: flex;
}

.card--image {
    flex: 100%;
    margin-top: 1rem;
    border-radius: 10px;
}

We have already discussed margin, display, and border-radius. In .card, display is set to flex, which means the elements will behave like block elements, and the display will be set according to the flexbox model. By using the shorthand property flex:100%;, you set the value for flex-grow, flex-shrink, and flex-basis. You can read more about it at the Mozilla Developer Network.

The final CSS blocks involve media queries. By using the @media rule, you can apply different styles for different media types/devices:

react-photo-search/src/App.css
...
.card--image {
    flex: 100%;
    margin-top: 1rem;
    border-radius: 10px;
}

@media (min-width: 768px) {
  .form {
    grid-template-columns: auto 1fr auto;
    grid-gap: 1rem;
    align-items: center;
  }
  .input {
    margin-bottom: 0;
  }
}

@media only screen and (max-width: 600px) {
    .card-list {
        column-count: 1;
    }
}

According to this code, column-count will change from 3 to 1 when the browser window is 600px or less (applicable for most mobile devices). This used the max-width property with the @media rule. The code before that uses min-width, which changes the style of the elements inside the @media rule when the width is 768px or more.

grid-template-columns is used to specify columns in the grid model. The number of columns is equal to the number of values passed, which is three according to the code (auto 1fr auto). The first and third grid element’s size will be according to their container size or the content’s size. The second element will be given 1fr (Fractional Unit), or the space left after the first and third elements have occupied according to their size. These three elements will be a camera emoji, the search input field, and the Search button. After the emoji and the button have taken the space according to their size, the rest of the area will go to the search input field, and it will change its width accordingly.

grid-gap: 1rem; creates a space of 1rem between two grid lines. align-items:center; positions the items in the center of the container.

This finishes the styling of your application. Save and exit from src/App.css. If you’d like to see the whole CSS file together, take a look at the GitHub repository for this code.

Now that you have installed the necessary dependency and added the custom CSS needed to style your project, you can move forward to the next section and design the UI or layout of the project.

Step 4 — Designing the User Interface

In this section, you will design the UI of the project. This will include elements like a heading, label, input field, and button.

Open the src/App.js file with the following command:

  • nano src/App.js

To add a heading to your project, create a div with the className="container" inside your App.js. Inside this div add an h1 tag with the className="title" and write React Photo Search inside the tag. This will be the title heading:

react-photo-search/src/App.js
import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <div className="container">
        <h1 className="title">React Photo Search</h1>
      </div>
    </div>
  );
}

Save and exit the file. In your browser, your app will now show your title:

Application with "React Photo Search" Title

Next, you will create a form that will take input from the user. This form will consist of an input text field and a submit button.

For this, create a new component named <SearchPhotos />. It is not necessary to create a separate component, but as you develop this project, splitting code into components makes it easier to write and maintain code.

In the src folder, create and open a new file called searchPhotos.js with the following command:

  • nano src/searchPhotos.js

Inside searchPhotos.js, you export a functional component named <SearchPhotos />:

react-photo-search/src/searchPhotos.js
import React from "react";

export default function SearchPhotos() {
  return (
    <>

    </>
  );
}

This is the basic structure of a functional component that you need to add to the searchPhotos.jsfile. Save this file.

The next step is to import and use the SearchPhotos component in App.js.

In a new terminal window, open up App.js:

  • nano src/App.js

Add the following highlighted lines to App.js:

react-photo-search/src/App.js
import React from "react";
import "./App.css";
import SearchPhotos from "./searchPhotos"

function App() {
  return (
    <div className="App">
      <div className="container">
        <h1 className="title">React Photo Search</h1>
        <SearchPhotos />

      </div>
    </div>
  );
}
export default App;

Save this file.

To create the search form, you will use the form tag and inside it, create an input field using the input tag and a button using the button tag.

Give the elements the className of their respective tags. While you are doing this, add a label with a camera emoji inside it for styling:

react-photo-search/src/searchPhotos.js
...
export default function SearchPhotos() {
  return (
    <>
      <form className="form"> 
        <label className="label" htmlFor="query"> 
          {" "}
          📷
        </label>
        <input
          type="text"
          name="query"
          className="input"
          placeholder={`Try "dog" or "apple"`}
        />
        <button type="submit" className="button">
          Search
        </button>
      </form>
    </>
  );
}

First, you created a form element with a className="form", and inside it a label with a camera emoji. Then comes the input element with attributes type="text", since the search query will be a string. The name="query" attribute specifies the name of the input element, className="input" gives the element a class for styling, and the placeholder value for the search bar is set to Try "dog" or "apple". The final element in form is a button with the type="submit".

Save and exit the file. Your app will now have a search bar after the title:

Application with search bar and placeholder text of 'Try "dog" or "apple"'

Now that the UI of the app is complete, you can start working on the functionalities by first storing the input query from the user in the next section.

Step 5 — Setting State Using Search Query

In this step, you will learn about states and React Hooks and then use them to store user input.

Now that you have constructed your application’s basic structure, we can discuss the React side of things. You have a form, but it doesn’t do anything yet, so the first thing to do is to take the input from the search bar and access it. You can do this with states.

States at their core are objects that are used to store the property values of components. Every time the state changes, the component re-renders. For this app, you need a state that will store the input or query from the search bar whenever the Search button is clicked.

One of the things that you may have noticed is that this project is using functional components. This allows you to use React Hooks to manage state. Hooks are functions that use React features like defining a state without writing a class. In this tutorial, you will make use of the useState() Hook.

The first thing to do is import useState inside your searchPhotos.js file.

Open up the file:

  • nano src/searchPhotos.js

Modify the first line of searchPhotos.js file to the following:

react-photo-search/src/searchPhotos.js
import React, { useState } from "react";

export default function SearchPhotos() {
...

Next, you will implement useState(). This is the syntax for the useState() Hook:

useState(initialState)

useState() returns the current state and a function commonly known as an updater function. To store these, you can use array destructuring:

const [query, setQuery] = useState(initialState);

In this example, query stores the current state of the component, and setQuery is a function that can be called to update the state. initialState defines the initial state value; it can be a string, a number, an array, or an object depending on the use.

In your project, the input from the search bar is a string, so you will use an empty string as an initial value of the state.

In your searchPhotos.js file, add the following line of code:

react-photo-search/src/searchPhotos.js
...

export default function SearchPhotos() {
  const [query, setQuery] = useState("");

  return (
    <>
      <form className="form">
        <label className="label" htmlFor="query">
          {" "}
          📷
        </label>
...

The next step is to set the value of the input text field to query and add an onChange() event to it. This onChange() event will have a function, inside which setQuery() will be used to update the state. The input string is retrieved using e.target.value:

react-photo-search/src/searchPhotos.js
...
<input
    type="text"
    name="query"
    className="input"
    placeholder={`Try "dog" or "apple"`}
    value={query}
    onChange={(e) => setQuery(e.target.value)}
/>
...

Now, the state and the input field’s values are interlinked, and you can use this search query to search for the image.

You can view the input from the search bar inside the query in real-time for testing purposes. Add console.log(query) just after where you defined state:

react-photo-search/src/searchPhotos.js
...
export default function SearchPhotos() {
   const [query, setQuery] = useState("");
   console.log(query);

  return (
    <>
    //
    </>
  );
}

Save the file.

You will now receive the input queries inside the console. You can open your console, using F12 in Chrome or Ctrl+Shift+K in Firefox:

Browser console demonstrating the logging of the user input for this application.

Now, searchPhotos.js will look like this:

react-photo-search/src/searchPhotos.js

import React, { useState } from "react";
export default function SearchPhotos() {
  const [query, setQuery] = useState("");
  console.log(query);

  return (
    <>
      <form className="form">
        <label className="label" htmlFor="query">
          {" "}
          📷
        </label>
        <input
          type="text"
          name="query"
          className="input"
          placeholder={`Try "dog" or "apple"`}
          value={query}
          onChange={(e) => setQuery(e.target.value)}
        />
        <button type="submit" className="button">
          Search
        </button>
      </form>
    </>
  );
}

This section discussed states and React Hooks and stored the user input in the input field inside the query state. In the next section, you will use this search query to search for the image and store the response inside another state.

Step 6 — Making API Requests to Unsplash

You will now use the unsplash-js library to search for images using the query from the input field. The response will be stored inside another state named pics.

You have already installed the unsplash-js library, so import it in searchPhotos.js file. You can also remove the console.log() statement from the previous section:

react-photo-search/src/searchPhotos.js
import React, { useState } from "react";
import Unsplash, { toJson } from "unsplash-js";

...

toJson is a helper function in the unsplash-js library that is used to convert the response into JSON format. You can learn more about helper functions at the unsplash-js GitHub page.

To use Unsplash in your app, make an instance of it using the new keyword like this:

react-photo-search/src/searchPhotos.js
import React, { useState } from "react";
import Unsplash, { toJson } from "unsplash-js";

const unsplash = new Unsplash({
  accessKey: "your_Access_Key",
});

Paste your Unsplash Access Key to replace your_Access_Key and you can now make API requests.

Warning: One should never share any access keys or Client ID’s for an API or any service. Potential bad actors can misuse them over the internet. In this scenario, they can make an unusual amount of requests that can be flagged as spam by your service provider, which can deactivate your application and account.

Now you will create an asynchronous function that will be triggered when clicking the Search button.

Just after where you defined state for query, define an async function:

react-photo-search/src/searchPhotos.js
...
export default function SearchPhotos() {
  const [query, setQuery] = useState("");

  const searchPhotos = async (e) => {
    e.preventDefault();
    console.log("Submitting the Form")
  };

Here e.preventDefault() stops the page from reloading whenever the Search button is clicked. You can pass this function in the onSubmit event inside the form tag. You can read more about this in the official React docs.

react-photo-search/src/searchPhotos.js
...
  return (
    <>
      <form className="form" onSubmit={searchPhotos}>
...

Save the file. Now, if you click the Search button, you will receive Submitting the Form in the console. You can remove this console.log() after a successful response in the console.

Inside your searchPhotos() function, you will use the Unsplash instance (unsplash). You can use the search method for searching the images. Here is the syntax for that:

search.photos(keyword, page, per_page, filters)

Here is the code to search for an image; add this code inside your searchPhotos() function:

react-photo-search/src/searchPhotos.js
...
const searchPhotos = async (e) => {
  e.preventDefault();
  unsplash.search
    .photos(query)
    .then(toJson)
    .then((json) => {
      console.log(json);
    });
};
...

First, you use unsplash.search and then specify what to search for, which is in this case photos. We can also search for users or collections. photos takes the first required argument as the keyword to search for, which is query; you can also specify the page, responses per page, image orientation, etc., through the optional arguments. For this tutorial, you only need the page and per_page arguments, limiting the response items you get from Unsplash.

Here are all the arguments that can be provided in photos

Argument Type Opt/Required Default
keyword string Required
page number Optional
per_page number Optional 10
filters object Optional
filters.orientation string Optional
filters.collections array Optional

You can learn more about them at the unsplash-js GitHub page.

You use the toJson method to convert the response into JSON, and finally, console.log() the response to test that API requests are made without any error. You will remove this console .log () in the next steps.

Save the file. Now open your console and click the Search button. You will find a response JSON like this:

{
  "results": [{
     "description": "Pink Wall Full of Dogs",
     "alt_description": "litter of dogs fall in line beside wall",
     "urls": {
           "raw": "https://images.unsplash.com/photo-1529472119196-cb724127a98e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjE0MTQxN30",
           "full": "https://images.unsplash.com/photo-1529472119196-cb724127a98e?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0MTQxN30",
           "regular": "https://images.unsplash.com/photo-1529472119196-cb724127a98e?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjE0MTQxN30",
           "small": "https://images.unsplash.com/photo-1529472119196-cb724127a98e?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0MTQxN30",
           "thumb": "https://images.unsplash.com/photo-1529472119196-cb724127a98e?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjE0MTQxN30"
                },
    ...
}

You can remove or comment the console.log() statement when you find a successful response from the Unsplash API, which means your code is working fine. This app will use the "urls" field, since that will be the source of the image.

You’ve now used the query from the user to search for images when the Search button was clicked using the unsplash-js library. Next, you will store the response inside another state named pics and display the images by mapping the elements inside this state.

Step 7 — Displaying Images on the Webpage

In this last section, you will store the response from Unsplash API inside another state named pics and then map over the elements of this state to display the images on the webpage.

To show images, you need to access the response JSON, and for that, another state will be needed. The previous state query stored queries from the user, which was used to make requests to the Unsplash API. This state pics will store the image response you get from Unsplash API.

In searchPhotos.js define another state like this:

react-photo-search/src/searchPhotos.js
...
  const [query, setQuery] = useState("");
  const [pics, setPics] = useState([]);
...

This state has been initialized with an empty array, and all the responses will be stored as an object inside this state. In other words, this is an array of objects.

To update this state with the JSON, you will use setPics inside unsplash API request:

react-photo-search/src/searchPhotos.js
...
    unsplash.search
      .photos(query, 1, 20)
      .then(toJson)
      .then((json) => {
        setPics(json.results);
  });
...

Now every time you search for a new query, this state will be updated accordingly.

Next, create a div with the className="card-list" just after where form tags end:

react-photo-search/src/searchPhotos.js
...
        <button type="submit" className="button">
          Search
        </button>
      </form>
      <div className="card-list">
      </div>
    </>
  );
}

Inside this div, you will map through the state and display the id of the image:

react-photo-search/src/searchPhotos.js
...
        <button type="submit" className="button">
          Search
        </button>
      </form>
      <div className="card-list">
        {pics.map((pic) => pic.id )}
      </div>
    </>
  );
}

You first use {} to pass the JavaScript expression, inside which you use the .map() method on your state.

Save your file. If you search now, you will see ids associated with different objects on the webpage:

Application with overlapping ID results rendered on the webpage

This is messy, but this also means your application is working.

Instead of displaying pic.id, open up JSX inside the map function and create a new div with the className="card". This is going to be the container for each individual image:

react-photo-search/src/searchPhotos.js
...
        <button type="submit" className="button">
          Search
        </button>
      </form>
      <div className="card-list">
        {
          pics.map((pic) => <div className="card"></div>);
        }
      </div>
    </>
  );
}

You can now display an image inside this div:

react-photo-search/src/searchPhotos.js
...
        <button type="submit" className="button">
          Search
        </button>
      </form>
      <div className="card-list">
        {
          pics.map((pic) => 
            <div className="card">
              <img
                className="card--image"
                alt={pic.alt_description}
                src={pic.urls.full}
                width="50%"
                height="50%"
              ></img>
            </div>);
        }
      </div>
    </>
  );
}

If you go back and see the response JSON, you will find a different kind of information. "urls" contains the path to the image, so here pic.urls.full is the actual path to the image and pic.alt_description is the alt description of the picture.

There are different fields inside "urls" that give different data, such as:

raw : Actual raw image taken by a user.
full : Raw image in .jpg format.
regular : Best for practical uses, width=1080px.
small : Perfect for slow internet speed, width=400px.
thumb : Thumbnail version of the image, width=200px.

In this tutorial, you are using full, but you can experiment with other types, too. You have also given a default height and width to the image.

Save your file.

Your application is almost finished; if you search now, you will be able to see your application in action. But there is still a small line of code left. If you search for your image and go to your console in the browser, you will see a warning.

Web console
Warning: Each child in a list should have a unique "key" prop.

To fix this, pass a unique key to every child using the id of the image. This key prop explicitly tells React the identity of each child in a list; this also prevents children from losing state between renders:

react-photo-search/src/searchPhotos.js
...
      <div className="card-list">
        {pics.map((pic) =>
          <div className="card" key={pic.id}>
            <img
              className="card--image"
              alt={pic.alt_description}
              src={pic.urls.full}
              width="50%"
              height="50%"
            ></img>
          </div>)};
      </div>
    </>
  );
}

You can adjust the number of images you want to show by passing the corresponding argument to unsplash.search.photos().

Save and exit the file. You’ll now have a working photo search app:

Animation of searching the term "apple" in the application and getting image results of apples

In this section, you stored the response from Unsplash API inside the pics state and displayed the images by mapping over the elements in pics.

Conclusion

In this tutorial, you developed a React Photo Search app with the Unsplash API. In building the project, the tutorial discussed how to use React Hooks, query an API, and style a user interface.

There is much that can be done with this application to extend it. For example, you could add a Random button to display random images, create a checkbox to toggle between searching for photos or the users that posted them according to the user’s preference, add an infinite scroll to display more images, and more. You can also use the same concept and make other projects involving API requests, like the Hacker News API.

If you would like to look at more React tutorials, check out our React Topic page.

Creative Commons License