One place for hosting & domains

      Search

      How To Perform Full-text Search in MongoDB


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      MongoDB queries that filter data by searching for exact matches, using greater-than or less-than comparisons, or by using regular expressions will work well enough in many situations. However, these methods fall short when it comes to filtering against fields containing rich textual data.

      Imagine you typed “coffee recipe” into a web search engine but it only returned pages that contained that exact phrase. In this case, you may not find exactly what you were looking for since most popular websites with coffee recipes may not contain the exact phrase “coffee recipe.” If you were to enter that phrase into a real search engine, though, you might find pages with titles like “Great Coffee Drinks (with Recipes!)” or “Coffee Shop Drinks and Treats You Can Make at Home.” In these examples, the word “coffee” is present but the titles contain another form of the word “recipe” or exclude it entirely.

      This level of flexibility in matching text to a search query is typical for full-text search engines that specialize in searching textual data. There are multiple specialized open-source tools for such applications in use, with ElasticSearch being an especially popular choice. However, for scenarios that don’t require the robust search features found in dedicated search engines, some general-purpose database management systems offer their own full-text search capabilities.

      In this tutorial, you’ll learn by example how to create a text index in MongoDB and use it to search the documents in the database against common full-text search queries and filters.

      Prerequisites

      To follow this tutorial, you will need:

      Note: The linked tutorials on how to configure your server, install MongoDB, and secure the MongoDB installation refer to Ubuntu 20.04. This tutorial concentrates on MongoDB itself, not the underlying operating system. It will generally work with any MongoDB installation regardless of the operating system as long as authentication has been enabled.

      Step 1 — Preparing the Test Data

      To help you learn how to perform full-text searches in MongoDB, this step outlines how to open the MongoDB shell to connect to your locally-installed MongoDB instance. It also explains how to create a sample collection and insert a few sample documents into it. This sample data will be used in commands and examples throughout this guide to help explain how to use MongoDB to search text data.

      To create this sample collection, connect to the MongoDB shell as your administrative user. This tutorial follows the conventions of the prerequisite MongoDB security tutorial and assumes the name of this administrative user is AdminSammy and its authentication database is admin. Be sure to change these details in the following command to reflect your own setup, if different:

      • mongo -u AdminSammy -p --authenticationDatabase admin

      Enter the password you set during installation to gain access to the shell. After providing the password, your prompt will change to a greater-than sign:

      Note: On a fresh connection, the MongoDB shell will connect to the test database by default. You can safely use this database to experiment with MongoDB and the MongoDB shell.

      Alternatively, you could switch to another database to run all of the example commands given in this tutorial. To switch to another database, run the use command followed by the name of your database:

      To understand how full-text search can be applied to documents in MongoDB, you’ll need a collection of documents you can filter against. This guide will use a collection of sample documents that include names and descriptions of several different types of coffee drinks. These documents will have the same format as the following example document describing a Cuban coffee drink:

      Example Cafecito document

      {
          "name": "Cafecito",
          "description": "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam."
      }
      

      This document contains two fields: the name of the coffee drink and a longer description which provides some background information about the drink and its ingredients.

      Run the following insertMany() method in the MongoDB shell to create a collection named recipes and, at the same time, insert five sample documents into it:

      • db.recipes.insertMany([
      • {"name": "Cafecito", "description": "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam."},
      • {"name": "New Orleans Coffee", "description": "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory."},
      • {"name": "Affogato", "description": "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream."},
      • {"name": "Maple Latte", "description": "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup."},
      • {"name": "Pumpkin Spice Latte", "description": "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree."}
      • ])

      This method will return a list of object identifiers assigned to the newly inserted objects:

      Output

      { "acknowledged" : true, "insertedIds" : [ ObjectId("61895d2787f246b334ece911"), ObjectId("61895d2787f246b334ece912"), ObjectId("61895d2787f246b334ece913"), ObjectId("61895d2787f246b334ece914"), ObjectId("61895d2787f246b334ece915") ] }

      You can verify that the documents were properly inserted by running the find() method on the recipes collection with no arguments. This will retrieve every document in the collection:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." } . . .

      With the sample data in place, you’re ready to start learning how to use MongoDB’s full-text search features.

      Step 2 — Creating a Text Index

      To start using MongoDB’s full-text search capabilities, you must create a text index on a collection. Indexes are special data structures that store only a small subset of data from each document in a collection separately from the documents themselves. There are several types of indexes users can create in MongoDB, all of which help the database optimize search performance when querying the collection.

      A text index, however, is a special type of index used to further facilitate searching fields containing text data. When a user creates a text index, MongoDB will automatically drop any language-specific stop words from searches. This means that MongoDB will ignore the most common words for the given language (in English, words like “a”, “an”, “the”, or “this”).

      MongoDB will also implement a form of suffix-stemming in searches. This involves MongoDB identifying the root part of the search term and treating other grammar forms of that root (created by adding common suffixes like “-ing”, “-ed”, or perhaps “-er”) as equivalent to the root for the purposes of the search.

      Thanks to these and other features, MongoDB can more flexibly support queries written in natural language and provide better results.

      Note: This tutorial focuses on English text, but MongoDB supports multiple languages when using full-text search and text indexes. To learn more about what languages MongoDB supports, refer to the official documentation on supported languages.

      You can only create one text index for any given MongoDB collection, but the index can be created using more than one field. In our example collection, there is useful text stored in both the name and description fields of each document. It could be useful to create a text index for both fields.

      Run the following createIndex() method, which will create a text index for the two fields:

      • db.recipes.createIndex({ "name": "text", "description": "text" });

      For each of the two fields, name and description, the index type is set to text, telling MongoDB to create a text index tailored for full-text search based on these fields. The output will confirm the index creation:

      Output

      { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 }

      Now that you’ve created the index, you can use it to issue full-text search queries to the database. In the next step, you’ll learn how to execute queries containing both single and multiple words.

      Step 3 — Searching for One or More Individual Words

      Perhaps the most common search problem is to look up documents containing one or more individual words.

      Typically, users expect the search engine to be flexible in determining where the given search terms should appear. As an example, if you were to use any popular web search engine and type in “coffee sweet spicy”, you likely are not expecting results that will contain those three words in that exact order. It’s more likely that you’d expect a list of web pages containing the words “coffee”, “sweet”, and “spicy” but not necessarily immediately near each other.

      That’s also how MongoDB approaches typical search queries when using text indexes. This step outlines how MongoDB interprets search queries with a few examples.

      To begin, say you want to search for coffee drinks with spices in their recipe, so you search for the word spiced alone using the following command:

      • db.recipes.find({ $text: { $search: "spiced" } });

      Notice that the syntax when using full-text search is slightly different from regular queries. Individual field names — like name or description — don’t appear in the filter document. Instead, the query uses the $text operator, telling MongoDB that this query intends to use the text index you created previously. You don’t need to be any more specific than that because, as you may recall, a collection may only have a single text index. Inside the embedded document for this filter is the $search operator taking the search query as its value. In this example, the query is a single word: spiced.

      After running this command, MongoDB produces the following list of documents:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree." } { "_id" : ObjectId("61895d2787f246b334ece912"), "name" : "New Orleans Coffee", "description" : "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory." }

      There are two documents in the result set, both of which contain words resembling the search query. While the New Orleans Coffee document does have the word spiced in the description, the Pumpkin Spice Late document doesn’t.

      Regardless, it was still returned by this query thanks to MongoDB’s use of stemming. MongoDB stripped the word spiced down to just spice, looked up spice in the index, and also stemmed it. Because of this, the words spice and spices in the Pumpkin Spice Late document matched the search query successfully, even though you didn’t search for either of those words specifically.

      Now, suppose you’re particularly fond of espresso drinks. Try looking up documents with a two-word query, spiced espresso, to look for a spicy, espresso-based coffee.

      • db.recipes.find({ $text: { $search: "spiced espresso" } });

      The list of results this time is longer than before:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece914"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup." } { "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." } { "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." } { "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree." } { "_id" : ObjectId("61895d2787f246b334ece912"), "name" : "New Orleans Coffee", "description" : "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory." }

      When using multiple words in a search query, MongoDB performs a logical OR operation, so a document only has to match one part of the expression to be included in the result set. The results contain documents containing both spiced and espresso or either term alone. Notice that words do not necessarily need to appear near each other as long as they appear in the document somewhere.

      Note: If you try to execute any full-text search query on a collection for which there is no text index defined, MongoDB will return an error message instead:

      Error message

      Error: error: { "ok" : 0, "errmsg" : "text index required for $text query", "code" : 27, "codeName" : "IndexNotFound" }

      In this step, you learned how to use one or multiple words as a text search query, how MongoDB joins multiple words with a logical OR operation, and how MongoDB performs stemming. Next, you’ll use a complete phrase in a text search query and begin using exclusions to narrow down your search results further.

      Step 4 — Searching for Full Phrases and Using Exclusions

      Looking up individual words might return too many results, or the results may not be precise enough. In this step, you’ll use phrase search and exclusions to control search results more precisely.

      Suppose you have a sweet tooth, it’s hot outside, and coffee topped with ice cream sounds like a nice treat. Try finding an ice cream coffee using the basic search query as outlined previously:

      • db.recipes.find({ $text: { $search: "ice cream" } });

      The database will return two coffee recipes:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." } { "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }

      While the Affogato document matches your expectations, Cafecito isn’t made with ice cream. The search engine, using the logical OR operation, accepted the second result just because the word cream appears in the description.

      To tell MongoDB that you are looking for ice cream as a full phrase and not two separate words, use the following query:

      • db.recipes.find({ $text: { $search: ""ice cream"" } });

      Notice the backslashes preceding each of the double quotes surrounding the phrase: "ice cream". The search query you’re executing is "ice cream", with double quotes denoting a phrase that should be matched exactly. The backslashes () escape the double quotes so they’re not treated as a part of JSON syntax, since these can appear inside the $search operator value.

      This time, MongoDB returns a single result:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." }

      This document matches the search term exactly, and neither cream nor ice alone would be enough to count as a match.

      Another useful full-text search feature is the exclusion modifier. To illustrate how to this works, first run the following query to get a list of all the coffee drinks in the collection based on espresso:

      • db.recipes.find({ $text: { $search: "espresso" } });

      This query returns four documents:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece914"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup." } { "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." } { "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree." } { "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }

      Notice that two of these drinks are served with milk, but suppose you want a milk-free drink. This is a case where exclusions can come in handy. In a single query, you can join words that you want to appear in the results with those that you want to be excluded by prepending the word or phrase you want to exclude with a minus sign (-).

      As an example, say you run the following query to look up espresso coffees that do not contain milk:

      • db.recipes.find({ $text: { $search: "espresso -milk" } });

      With this query, two documents will be excluded from the previously returned results:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." } { "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }

      You can also exclude full phrases. To search for coffees without ice cream, you could include -"ice cream" in your search query. Again, you’d need to escape the double quotes with backslashes, like this:

      • db.recipes.find({ $text: { $search: "espresso -"ice cream"" } });

      Output

      { "_id" : ObjectId("61d48c31a285f8250c8dd5e6"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup." } { "_id" : ObjectId("61d48c31a285f8250c8dd5e7"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree." } { "_id" : ObjectId("61d48c31a285f8250c8dd5e3"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }

      Now that you’ve learned how to filter documents based on a phrase consisting of multiple words and how to exclude certain words and phrases from search results, you can acquaint yourself with MongoDB’s full-text search scoring.

      Step 5 — Scoring the Results and Sorting By Score

      When a query, especially a complex one, returns multiple results, some documents are likely to be a better match than others. For example, when you look for spiced espresso drinks, those that are both spiced and espresso-based are more fitting than those without spices or not using espresso as the base.

      Full-text search engines typically assign a relevance score to the search results, indicating how well they match the search query. MongoDB also does this, but the search relevance is not visible by default.

      Search once again for spiced espresso, but this time have MongoDB also return each result’s search relevance score. To do this, you could add a projection after the query filter document:

      • db.recipes.find(
      • { $text: { $search: "spiced espresso" } },
      • { score: { $meta: "textScore" } }
      • )

      The projection { score: { $meta: "textScore" } } uses the $meta operator, a special kind of projection that returns specific metadata from returned documents. This example returns the documents’ textScore metadata, a built-in feature of MongoDB’s full-text search engine that contains the search relevance score.

      After executing the query, the returned documents will include a new field named score, as was specified in the filter document:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream.", "score" : 0.5454545454545454 } { "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam.", "score" : 0.5384615384615384 } { "_id" : ObjectId("61895d2787f246b334ece914"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup.", "score" : 0.55 } { "_id" : ObjectId("61895d2787f246b334ece912"), "name" : "New Orleans Coffee", "description" : "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory.", "score" : 0.5454545454545454 } { "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree.", "score" : 2.0705128205128203 }

      Notice how much higher the score is for Pumpkin Spice Latte, the only coffee drink that contains both the words spiced and espresso. According to MongoDB’s relevance score, it’s the most relevant document for that query. However, by default, the results are not returned in order of relevance.

      To change that, you could add a sort() clause to the query, like this:

      • db.recipes.find(
      • { $text: { $search: "spiced espresso" } },
      • { score: { $meta: "textScore" } }
      • ).sort(
      • { score: { $meta: "textScore" } }
      • );

      The syntax for the sorting document is the same as that of the projection. Now, the list of documents is the same, but their order is different:

      Output

      { "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree.", "score" : 2.0705128205128203 } { "_id" : ObjectId("61895d2787f246b334ece914"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup.", "score" : 0.55 } { "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream.", "score" : 0.5454545454545454 } { "_id" : ObjectId("61895d2787f246b334ece912"), "name" : "New Orleans Coffee", "description" : "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory.", "score" : 0.5454545454545454 } { "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam.", "score" : 0.5384615384615384 }

      The Pumpkin Spice Latte document appears as the first result since it has the highest relevance score.

      Sorting results according to their relevance score can be helpful. This is especially true with queries containing multiple words, where the most fitting documents will usually contain multiple search terms while the less relevant documents might contain only one.

      Conclusion

      By following this tutorial, you’ve acquainted yourself with MongoDB’s full-text search features. You created a text index and wrote text search queries using single and multiple words, full phrases, and exclusions. You’ve also assessed the relevance scores for returned documents and sorted the search results to show the most relevant results first. While MongoDB’s full-text search features may not be as robust as those of some dedicated search engines, they are capable enough for many use cases.

      Note that there are more search query modifiers — such as case and diacritic sensitivity and support for multiple languages — within a single text index. These can be used in more robust scenarios to support text search applications. For more information on MongoDB’s full-text search features and how they can be used, we encourage you to check out the official official MongoDB documentation.



      Source link

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


      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:

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

      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:

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

      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:

      Open App.css with the following command:

      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:

      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:

      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.

      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:

      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

      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:

      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:

      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:

      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

      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.



      Source link

      Four Methods to Search Through Arrays in JavaScript


      In JavaScript, there are plenty of useful ways to find items in Arrays. You could always resort to the basic for loop, but with ES6+ there are plenty of methods to loop over the array and find what you need with ease.

      With so many different methods, which one do you use and in which case? For instance, when searching through an array, do you want to know if the element is in the array at all? Do you need the index of the element or the element itself?

      With each different method that we’ll cover, it’s important to understand that they’re all built-in methods on the Array.prototype. That means you simply need to chain them onto any array with dot notation. That also means these methods are not available on objects or anything else other than Arrays (though there is overlap with Strings).

      We’ll look at the following Array methods:

      includes

      const alligator = ["thick scales", 80, "4 foot tail", "rounded snout"];
      
      alligator.includes("thick scales"); // returns true
      

      The .includes() method returns a boolean value and is perfect for telling you whether an element exists in an array or not. It gives a simple true or false answer. This is the basic syntax:

      arr.includes(valueToFind, [fromIndex]);
      

      Now as you see in our example, we only had one parameter – the valueToFind. This is the value to match in the array. The optional fromIndex is a number, indicating what index you want to start looking from (default is 0, so the entire array is searched). So, since in our example the ‘thick scales’ item is at the 0 index, the following would be false: alligator.includes('thick scales', 1); since it starts searching from index 1 and on.

      Now, there are a few important things to notice. This .includes() method uses strict comparison. That means, from the example above, the following would return false: alligator.includes('80'); That’s because though 80 == '80' is true, 80 === '80' is false – different types won’t pass strict comparison.

      find

      How is .find() different from the includes() method? If in our example we only changed the text “includes” to “find” we would get this error:

      Uncaught TypeError: thick scales is not a function
      

      That’s because the find method requires a function to be passed in. That’s because the find method isn’t going to just use the simple comparison operator like “includes()” does. Instead, it will pass each element into your function and see whether it returns true or false. So, although this works: alligator.find(() => 'thick scales');, you would probably want to put your own comparison operator in the function for it to return anything relevant.

      const alligator = ["thick scales", 80, "4 foot tail", "rounded snout"];
      
      alligator.find(el => el.length < 12); // returns '4 foot tail'
      

      This simple function in our find method looks at each element of the array, with the alias of ‘el’ we assigned it, and stops when it finds the first one which is true. In our case, true is having a length property of less than 12 (numbers don’t have a length property). You could of course make this function as complex as needed, making your true condition meet your needs.

      Notice as well, this didn’t return true. The find method doesn’t return a boolean, but instead returns the first matching element. If there isn’t a matching element – as in nothing exists that meets the criteria defined in your function – it will return undefined. Also notice that it returns the first, so if there is more than one element in the array that meets the criteria, it will only grab the first instance. In our example, if there was another string of length less than 12 after ‘4 feet tall’ it wouldn’t change our result.

      In our example, we only employed the callback with one parameter. You can also add parameters to reference the current element’s index. Another parameter can be the entire array itself, but I find this rarely used. Here is an example using the index:

      alligator.find((el, idx) => typeof el === "string" && idx === 2); // returns '4 foot tall'
      

      We know in our array, there are 3 different elements that meet the first condition (typeof el === ‘string’). If this was our only condition, it would return the first one, ‘thick scales’. But the difference is, only one has the index of 2 and that is ‘4 foot tall’.

      Speaking of indexes, a similar array method is .findIndex(). This method also receives a function, but as you can guess, it returns the matching element’s index instead of the element itself.

      indexOf

      const alligator = ["thick scales", 80, "4 foot tail", "rounded snout"];
      
      alligator.indexOf("rounded snout"); // returns 3
      

      Like the .includes() method, .indexOf() uses strict comparison, not a function as we saw with the .find() method. But, unlike includes(), it returns the index of the element, rather than a boolean. You can also indicate which index in the array to start searching at.

      I find indexOf() to be very useful. It’s quick and easy, can tell you where the element is in the array, and can tell you whether the element exists. How does it tell you if the element exists? Basically, we can know the element exists if it returns a positive number, and if it returns -1 we know the element doesn’t exist.

      alligator.indexOf("soft and fluffy"); // returns -1
      alligator.indexOf(80); // returns 1
      alligator.indexOf(80, 2); // returns -1
      

      And as you can see, although we could get the find() or findIndex() methods to give us the same information, this is a whole lot less to write. We don’t have to write out a function for comparison, as it is already within the indexOf method.

      Now, like the others, indexOf() also returns the index of the first matching element it finds. JavaScript gives us an alternate array method .lastIndexOf(). As you can guess, this does the same thing as indexOf() but starting from the last index of the array and working backward. You can also specify a second parameter, but remember the indexes don’t change, just because you are using a different method.

      const alligator = ["thick scales", 80, "4 foot tail", "rounded snout", 80];
      
      alligator.indexOf(80); // returns 1
      alligator.lastIndexOf(80); // returns 4
      alligator.indexOf(80, 2); // returns 4
      alligator.lastIndexOf(80, 4); // returns 4
      alligator.lastIndexOf(80, 3); // returns 1
      

      Bonus: filter

      const alligator = ["thick scales", 80, "4 foot tail", "rounded snout", 80];
      
      alligator.filter(el => el === 80); //returns [80, 80]
      

      The filter() method is like the find() method, in that it requires a function passed and a condition for what will be returned. The main difference is, filter() always returns an array, even if there is only one matching element. But it will return all the matching elements, whereas find() only returns the first matching.

      The important thing with filter is that it returns all the elements matching your criteria. It could only be me, but I can get confused, thinking “these are the elements I want to filter out”, when truthfully, you are indicating the elements you wish to filter in.

      Conclusion

      The easiest method I find to use, when searching for something is the find() method, but as you can see it really depends on your case.

      • Do you need to know only if it exists? Use .includes().
      • Do you need to get the element itself? Use .find(), or .filter() for multiple items.
      • Do you need to find the index of the element? Use .indexOf() or findIndex() for a more complex search.

      The arrays in the examples here were very simple. You may find yourself with an array of objects. Here are some very basic examples below to navigate through the jungle of nested objects:

      const jungle = [
        { name: "frog", threat: 0 },
        { name: "monkey", threat: 5 },
        { name: "gorilla", threat: 8 },
        { name: "lion", threat: 10 }
      ];
      
      // break the object down in order to use .includes() or .indexOf()
      const names = jungle.map(el => el.name); // returns ['frog', 'monkey', 'gorilla', 'lion']
      console.log(names.includes("gorilla")); // returns true
      console.log(names.indexOf("lion")); // returns 3 - which corresponds correctly assuming no sorting was done
      
      // methods we can do on the array of objects
      console.log(jungle.find(el => el.threat == 5)); // returns object - {name: "monkey", threat: 5}
      console.log(jungle.filter(el => el.threat > 5)); // returns array - [{name: "gorilla", threat: 8}, {name: 'lion', threat: 10}]
      



      Source link