Cloudinary Blog

Building a Smart AI Image Search Tool Using React - Part 1: App Structure and Container Component

Building a Smart AI Image Search Tool Using React

What if we could create a search service for images? Type in a word and get images with titles or descriptions matching our search. Better yet, what if we could create a search service for images but rather than matching just titles and image description, we can search for something in an image, regardless of the given image title or description. For example, find one with a dog in it, or those that may have a street lamp or a bus (more like an image search tool).

Few artificial intelligence examples out there show what their underlying technology looks like. Hence, in this two-part article, we will show how to build a smart search service to serve as an image search tool, not just based on the title but also on image content. This app will mainly use React, Cloudinary and Algolia Search. In this first article, we will be building the parent component of our app, which is a stateful component.


You don’t need to know how to write artificial intelligence code to follow along (in as much as we are using AI technology). Basic knowledge of HTML, CSS, JavaScript and React is required. Also knowledge of technologies such as Express, Axios and Webtask offers added advantage, but is not necessary.


Node.js is used to build the backend server in this service and its package manager npm is required to install essential node modules for this project.

Download Node and npm here. You can verify if they are installed by running this on your command line:

node -v
npm -v

Versions of both tools should display on your console.

React, a front-end JavaScript library, is used for front-end user interaction. The popular create-react-app build tool will be used for quick build. Create a React project by installing create-react-app with:

npm install -g create-react-app

Now, we can build a React app from scratch quickly with:

create-react-app smart-search

This creates a simple React app. cd into smart-search and run the following command in the console to start the app:

    npm run start


You have successfully created a simply React app. Now we will tailor it to our app, which has two primary functions - upload and search.

This app has several dependencies and modules required to function. The dependencies are:

  • Algoliasearch: Used to initiate Algolia AI search in our app
  • axios: Required to make communications to the server from the client side
  • body-parser: Parses incoming requests in a middleware
  • classnames: Used to dynamically join classnames in JavaScript
  • Cloudinary: Cloudinary provides an awesome image storage, transformation and delivery solution for both web and mobile
  • connect-multiparty: Connect middleware for multiparty
  • Express: A Node.js framework for creating node servers
  • react-instantsearch: React search library by Algolia
  • webtask-tools: Webtask for a serverless architecture

Install these dependencies by running this on your command line:

npm install --save algoliasearch axios body-parser classnames cloudinary cloudinary-react connect-multiparty express react-instantsearch webtask-tools

Using the —save flag ensures that the dependencies are saved locally in the package.json and can be found in the node_modules folder.

On the client side, in the index.html file, Cloudinary is required and included in script tags at the bottom of the HTML script just before the closing </body> tag. Include these scripts:


Also, in the head tag of the HTML script, Bulma and the react-instant-search-theme-algolia css files are imported. These are used to style our app. Bulma is a free and open-source CSS framework based on flexbox. Include these link tags in the head:

<link rel="stylesheet" href="">
<link rel="stylesheet" href="">

Still, in the index.html file in the public folder, you can change the title of the page to whichever title you prefer.

Now that we have all dependencies installed, it would be nice to build out the front-end first, before building the server on the backend.

Building the Front-end

In the front-end part of our image search tool, we will be making use of the React component architecture, which includes the stateful and stateless components. In the app currently, the create-react-app tool has helped with creating a singular stateful component in App.js and its corresponding CSS file App.css. This is where the majority of the stateful configuration will occur.

For the stateless components, we will require components for the images modal and imagelist. In the src folder, create a folder called components. In the components folder, create the required components - Modal.js, ImageList.js and their corresponding CSS files - Modal.css, ImageList.js.

Now we have all the build files required, we willl develop the parent App.js component.!

Configure App.js

In the App.js file located in the src folder, clear all the code written so we can start fresh. First, import all required modules with:

import React, { Component } from 'react';
import {
} from 'react-instantsearch/dom';
import axios from 'axios';
import ImageList from './components/ImageList';
import Modal from './components/Modal';
import './App.css';

React, axios InstantSearch, SearchBox, and InfiniteHits are imported, as well as the components created (ImageList and Modal). Also, the style sheet for the parent App.js file is imported.

Next, we configure the App component. The App component is merely an extension of the React component class with defined constructor function and methods. The state of the app is defined in the constructor functions, as well as some required properties of the App class.

Create an empty class extending React component imported earlier and export it:

class App extends Component{
  //configuration goes in here
export default App;

In the App class, create a constructor function, and in here the state variables will be defined, as well as constructor properties.

class App extends Component {
    this.state = {
      modalIsActive: false,
      preview: '',
      previewPublicId: '',
      description: '',
      previewPayload: '',
      pending: false

export default App

In the state object defined in the constructor, you can see the various variables defined. Their individual states can be altered and events are based on their state at that time. The states defined are:

  • modalisActive: This represents the state of the modal used in uploading the images, it is assigned a Boolean and when it is true, the modal should be open and vice-versa.
  • preview: This is the URL of the uploaded asset available for preview.
  • previewPublicId: PublicId assigned to the uploaded image and serves as a unique identifier for each image.
  • description: A user-defined description for the image.
  • previewPayload: Once an image is uploaded, the data received from the Cloudinary servers are assigned to the preview payload.
  • pending: This is used to create some “blocking” ability in our app to prevent some other process from running when pending is true or run when pending is false.

Now we have our state props defined, let’s define some key constructor variables we will require in our app.

Include this in the constructor function:

this.requestURL =
      ''; = window.cloudinary;
this.uploadWidget =
  { cloud_name: 'christekh', upload_preset: 'idcidr0h' },
  (error, [data]) => {
      preview: data.secure_url,
      previewPayload: data,
      previewPublicId: data.public_id

First, we assign the webtask address for our server to the requestURL variable, find out about webtasks, serverless architectures and how to create a webtask here. However, you may want to hold off until the server is built at the end before shipping to webtask. The Cloudinary property is called on the window object and assigned to Now we can use the Cloudinary upload widget to handle uploads from both filesystem sources, URL and camera!

The createUploadWidget method is called on the Cloudinary window object and assigned to the this.uploadWidget variable. In createUploadWidget, the first parameter is an object of user details obtained from the Cloudinary dashboard. Create an account on Cloudinary and navigate to the dashboard to see your cloud name and upload_preset. The second parameter is a callback function with error as its first parameter and the returned data array as its second parameter.

In the function, we first log the returned data to the console (comes in handy when debugging) and change the state of the app by assigning properties from the returned data to our state variables. We are done with our constructor, let’s get to creating class methods required. In the App class, add:

toggleModal() {
    this.setState({ modalIsActive: !this.state.modalIsActive });
handleDropZoneClick(event) {;
handleDescriptionChange(event) {
  const { value } =;
  if (value.length <= 150) {
    this.setState({ description: value });

The toggleModal() function is used to change the state of the modalisActive so whenever the function is called the modal box is closed or if it is closed the modal box is open on the function call. Sounds like a good feature for close and open modal button.

The handleDropZoneClick() function takes event as a parameter and executes the open() method on the Cloudinary uploadWidget window.

Every image should require some form of short description to give details or a title for an image. The handleDropZoneClick() function also receives an event as a parameter, the ES6 object destructuring syntax is used to assign the value property to the value variable and a conditional statement is used to very if value is less than 150 characters, then the value of the description variable in state is changed to that put in by the user. At this point, we can upload and describe the image. Now it’s time to save it to our servers. Create a saveImage() function in the App class with:

saveImage() {
  const payload = {...this.state.previewPayload, {
    description: this.state.description
  this.setState({ pending: true });
  // Post to server + '/save', payload).then(data => {
    // Set preview state with uploaded image
    this.setState({ pending: false });
  }).catch(e => console.log(error));

What are we trying to save? The payload, in doing that the Object.assign() method is used to transfer the source object(second parameter) and the value of this.state.previewPayload into the object, payload. When saving the image, other possible operations are paused so the pending value is set to true and Axios is used to send a post request to the API endpoint on the server handling image receipt. Since this is an asynchronous operation the .then() method is used to change the state of the pending to false (so it is back to normal) and the modal is closed by calling the toggleModal() function. Let’s render elements to the DOM.

In the App class, create a render function next to return JSX elements with:

render() {
  return (
    <div className="App">
        <section className="hero is-info">
          <div className="hero-body">
            <div className="container">
              <div className="level">
                <div className="level-left">
                  <div className="level-item">
                      <h1 className="title">Smart Search</h1>
                      <h2 className="subtitle">
                        Smart auto tagging & search. Search results depend on
                        images' actual contents.
                <div className="level-right">
                  <div className="level-item">
                      className="button is-info is-inverted is-medium"
                      Add a new image

Here, the InstantSearch element created by the react-instantsearch package wraps the whole elements after the parent div with the className of App and credentials are passed as props to the InstantSearch element. The rest are HTML elements used to create a structure for the app with Bulma classes for styling. Notice the button that says ‘Add a new image’? This is responsible for opening and closing the upload modal. Just after the closing section tag, create another section for the SearchBox and the InfinteHits tag for the images and the upload modal with:

<section className="hero is-light">
  <div className="hero-body">
    <div className="container search-wrapper">
      <SearchBox />
<InfiniteHits hitComponent={ImageList} />

The top section holds the SearchBox component. Next is the InfiniteHits component which has a prop ImageList which is a component created. The infiniteHits components receive context from its parent component(InstantSearch) and return the images saved in a specified component(ImageList). The upload modal is created in the modal component and passed all the required props. Since we have this class methods as props, it will lose scope, so we have to rebind it to the constructor. In the constructor function, rebind the functions with:

this.toggleModal = this.toggleModal.bind(this);
this.saveImage = this.saveImage.bind(this);
this.handleDescriptionChange = this.handleDescriptionChange.bind(this);
this.handleDropZoneClick = this.handleDropZoneClick.bind(this);

In App.css, clear it and replace it with this styling:

.title:not(.is-spaced)+.subtitle {
  margin-top: 0;
.ais-InfiniteHits__root {
  column-count: 3;
  column-gap: 1em;
  width: 60%;
  margin: 50px auto;
.search-wrapper {
  display: flex;
  /* align-items: center; */
  justify-content: center;
.hero-body {
  padding: 1.5rem 1.5rem;


So far we have been able to build out the app structure for our image search tool with the main focus on the stateful component, App.js. In part two, we will show how to develop the stateful components required to render the images and the upload modal, as well as the backend server to handle requests and responses from Algolia-search, even though the server will ultimately be bundled into a Webtask.

Recent Blog Posts

Integrating Cloudinary with Forestry’s Media Library

At Forestry, we believe that there is a bright future for static HTML sites built with tools like Jekyll and Hugo. These tools can create sites that run well, and are easy to host and maintain, because they don’t require any server-side code.

Read more
Video Optimization With the HTML5 <video> Player

Lack of experience and compression knowhow can cause significant user-experience problems. For instance, on a major retail site, I recently ran into a 48 MB video-hero banner. Pulling out the video and encoding it as an H.264 MP4 reduces the size to 1.9 MB. So, despite the desire for more video content, developers have not yet caught up to best practices. How do we get the best of both worlds without creating a disaster like the one above?

Read more
Build a Facial Emotion Recognition Based Video Suggestion App

Developers are always looking for new and creative ways to deliver content that resonates with the way users feel. Often using the latest technical innovations the market has to offer such as Artificial Intelligence (AI) and Machine Learning (ML). What better way to demonstrate innovative uses of these technology in a consumer market than capturing expressions from your users and then serving content based on that expression!

Read more
Improve Customer Data Protection with GDPR Implementation


Yay! We've done it! Gold-Star for us! We've talked with all the people, made all the changes, paid all the lawyers and checked all the boxes. GDPR? ✅Done!

Not so fast. Of course, conforming to the GDPR regulations introduced in Europe is just the beginning. This is a process and a state of mind that must become part of our long-term cultural ethos.

Read more
Magento Image and Video Optimization

As the number of channels and devices continues to grow, it is becoming much more challenging to deliver an optimal visual experience. On an eCommerce site, engagement and conversions are critical. Factors such as page load time strongly influence search engine rankings, shopping experiences, conversion rates and, ultimately, your revenue. An optimized visual experience positively affects conversion rates. But it also can introduce resource bottlenecks, as every image and video needs to be delivered in the most efficient format, quality and resolution, based on the viewing device.

Read more