Build an Unsplash Photo Search App Using Vanilla JavaScript
Tags: javascript, html, css, beginners
Published: 2020-11-24 14:51:55

In this article, I will explain how I used JavaScript to build an image search app. It fetches and displays images from [Unsplash](unsplash.com) based on the user's search query. This is one of several personal projects I've built to enable me to gain some practice on all I've been learning. 

![My Unsplash search app](https://res.cloudinary.com/dipyaby4f/image/upload/v1604092951/2020-10-13_12.15.33_e9ecfae75723.png)

## Setting up the markup and styles
I thought about what I wanted the page to look like and decided on a simple white page (as shown above) with the input and submit button at the top, and the results in a grid formation. You can view the whole code below:

{% codepen https://codepen.io/Tolu-Agboola/pen/WNxOoqZ default-tab=html %}

## Tackling the JavaScript
I first globally stored references to the form and some other HTML elements that I was going to work with. I would still select others within functions later. I also added a `submit` event listener to the form.
```javascript
const form = document.querySelector('.js-form');
form.addEventListener('submit', handleSubmit);
const nextBtn = document.querySelector('.js-next');
const prevBtn = document.querySelector('.js-prev');
let resultStats = document.querySelector('.js-result-stats');
let searchQuery;
```
Then I defined the callback function `handleSubmit` in the event listener as shown below:
```javascript
function handleSubmit(event) {
	event.preventDefault();
	const inputValue = document.querySelector('.js-search-input').value;
	searchQuery = inputValue.trim();
	console.log(searchQuery);
	fetchResults(searchQuery);
}
```
This function takes an event as its argument and first of all prevents the page from reloading using the `preventDefault()` method. It then stores the value of the search input in `inputValue` and removes any whitespace with the `trim()` method. It stores the trimmed input value in `searchQuery` and passes it as an argument to the `fetchResults` function which is being called there. I logged the value of `searchQuery` to the console to make sure the right value was being passed.

### Fetch results from Unsplash
```javascript
async function searchUnsplash(searchQuery) {
	const endpoint = `https://api.unsplash.com/search/photos?query=${searchQuery}&client_id=YOUR_ACCESS_KEY`;
	const response = await fetch(endpoint);
	if (!response.ok) {
		throw Error(response.statusText);
	}
	const json = await response.json();
	return json;
}

async function fetchResults(searchQuery) {
	try {
		const results = await searchUnsplash(searchQuery);
		console.log(results);
		displayResults(results);
	} catch(err) {
		console.log(err);
		alert('Failed to search Unsplash');
	}
} 
```
To be able to use Unsplash's API, you have to create a developer account. Only after that do you get your unique API key with which you can access the photos on the site. An AJAX request is made to Unsplash using a URL containing the endpoint and the relevant query parameters. More information on this is provided in the [Documentation](unsplash.com/documentation) page on their website.

The function `searchUnsplash` takes one parameter (`searchQuery`), which is inserted into the endpoint as the value of the `query` query parameter. Now, the URL is stored in a variable `endpoint` which is passed as a parameter to `fetch`. The `fetch()` method takes one argument, the path to the resource you want to fetch, which is stored in `endpoint` in this case. It always returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). Now, if the response is 200 OK, it is parsed as JSON which is stored in the `json` variable. The result is logged to the console so as to view the contents of the JSON data.

Both functions above are asynchronous which means that the `await` keyword can be used to pause the execution of the function until a promise is resolved. This is achieved by placing the `async` keyword before declaring a function. I used a `try...catch` block in the `fetchResults` function. The `try` block 'tries' to execute the code within it and, should there be an exception or error, the `catch` block saves the day and responds appropriately with whatever code is written within it. This is a control flow mechanism which prevents the code from crashing if an error occurs while fetching the results. 

###Display the results on the page
The next thing is to display the results on the page. If you check the JSON data looged to the console, you will find that it contains several properties which have different contents.

![Chrome console containing raw JSON data](https://res.cloudinary.com/dipyaby4f/image/upload/v1604239194/2020-11-01_14.57.12_5a4befeb5bd3.png)

The `results` property is an array in which the search results are contained. Each search result is an object and can be accessed using either dot or bracket notation.

```javascript
function displayResults(json) {
	const searchResults = document.querySelector('.js-search-results');
	searchResults.textContent = '';
	json.results.forEach(result => {
		const url = result.urls.small;
		const unsplashLink = result.links.html;
		const photographer = result.user.name;
		const photographerPage = result.user.links.html;
		searchResults.insertAdjacentHTML(
			'beforeend',
			`<div>
				<a href="${unsplashLink}" target="_blank">
					<div class="result-item" style="background-image: url(${url});"></div>
				</a>
				<p class="photographer-name">
					<a href="${photographerPage}" target="_blank" style="color: black; text-decoration: none;">Photo by ${photographer}</a>
				</p>
			</div>`
		);	
	});
	totalResults = json.total;
	resultStats.textContent = `About ${totalResults} results found`;
};
```

An empty `div` with a class of `search-results` was already created in the HTML file. It is then selected in the JS file within a new function called `displayResults` which takes a JSON object as an argument. The `textContent` is also set to an empty string to clear all previous results.  

![JSON Object](https://res.cloudinary.com/dipyaby4f/image/upload/v1606212496/2020-11-24_10.28.47_9df83f297e27.png)

Now, the `results` array is iterated over using the `forEach` method. Within the callback function, the nested object can be accessed through the `result` parameter. If you study the above image closely, you will find that each object in the array contains several keys such as `links`, `user`, `urls`. These can be used to access different categories of information on the object in question.

The first lines within the callback function are variables in which the different values needed are stored. They were all accessed using dot notation and include:
- the image url
- the link to the image on Unsplash
- the name of the photographer
- the link to the photographer's Unsplash profile

Afterwards, each result is inserted into the `searchResults` element using the [insertAdjacentHTML](https://developer.mozilla.org/en/docs/Web/API/Element/insertAdjacentHTML) method. This method takes two arguments: the position of the element, and the text to be parsed as HTML. Template literals are used in the second argument because of the parts of the code that will be changing constantly. These are represented by placeholders in the code. The function `displayResults` is then called in `fetchResults`.  

Each image is set to be the background of its container, and is also a link to its Unsplash page. The name of the photographer, which links to his/her Unsplash profile, is placed right under the image and the result display was styled using CSS Grid.

###Show a loading indicator
This is something to be displayed when a search query is being executed to let the user know that the operation is still in progress. I selected a spinner from [this website](https://tobiasahlin.com/spinkit/) and pasted the HTML and CSS into my code. A reference to the spinner was stored globally in a variable `spinner` and then the `fectchResults` function was updated as follows:

```javascript
const spinner = document.querySelector('.js-spinner');

```

```javascript
async function fetchResults(searchQuery) {
	spinner.classList.remove('hidden');
	try {
		const results = await searchUnsplash(searchQuery);
		pagination(results.total_pages);
		console.log(results);
		displayResults(results);
	} catch(err) {
		console.log(err);
		alert('Failed to search Unsplash');
	}
	spinner.classList.add('hidden');
} 
```

###Conclusion
In this tutorial, we have seen how an interactive application can be built using JavaScript. I hope you learnt a lot from it. 

I'm currently open to job offers. Any information on openings will be really appreciated.

Thanks for reading!