Categories
JavaScript Vue

Add Multiple Selection Drop Down with Vue-Multiselect

Multiple selection is often a feature added to web apps to let users select multiple items at once.

Vue apps can use the Vue-Multiselect library to add this feature.

In this article, we’ll look at how to use this package to add a multi-select drop down.

Basic Single Select

We can get started by writing the following code:

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-multiselect@2.1.0"></script>
    <link
      rel="stylesheet"
      href="https://unpkg.com/vue-multiselect@2.1.0/dist/vue-multiselect.min.css"
    />
  </head>
  <body>
    <div id="app">
      <vue-multiselect v-model="value" :options="options"></vue-multiselect>
    </div>
    <script src="index.js"></script>
  </body>
</html>

index.js:

Vue.component("vue-multiselect", window.VueMultiselect.default);

new Vue({
  el: "#app",
  data: {
    value: "",
    options: ["apple", "orange", "grape"]
  }
});

In the code above, we added the Vue-Multiselect library with the following script and link tags in index.html:

<script src="https://unpkg.com/vue-multiselect@2.1.0"></script>
<link
  rel="stylesheet"
  href="https://unpkg.com/vue-multiselect@2.1.0/dist/vue-multiselect.min.css"
/>

for the code and styles respectively.

Then we registered the component with:

Vue.component("vue-multiselect", window.VueMultiselect.default);

Then we added the options for our dropdown choices:

options: ["apple", "orange", "grape"]

Finally, in our template, we added:

<vue-multiselect v-model="value" :options="options"></vue-multiselect>

After that, we should see a drop down showning on the screen with those choices listed in the options.

Single Select with Objects

In the code above, we have an array of strings, but we can also use an array of objects as options.

We just have to change our example above slightly to let user select objects:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-multiselect@2.1.0"></script>
    <link
      rel="stylesheet"
      href="https://unpkg.com/vue-multiselect@2.1.0/dist/vue-multiselect.min.css"
    />
  </head>
  <body>
    <div id="app">
      <vue-multiselect
        track-by="name"
        label="name"
        v-model="value"
        :options="options"
      ></vue-multiselect>
      <p>{{value}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

index.js:

Vue.component("vue-multiselect", window.VueMultiselect.default);

new Vue({
  el: "#app",
  data: {
    value: "",
    options: [
      { name: "Vue.js", language: "JavaScript" },
      { name: "Rails", language: "Ruby" },
      { name: "Sinatra", language: "Ruby" }
    ]
  }
});

In the code above, we added the track-by1 andlabelprops to enable the library to track changes in object by itsname` property.

Multiple Select

All we have to do to enable multiple selection is to set the multiple prop to true.

We just have to revise the example above by changing index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-multiselect@2.1.0"></script>
    <link
      rel="stylesheet"
      href="https://unpkg.com/vue-multiselect@2.1.0/dist/vue-multiselect.min.css"
    />
  </head>
  <body>
    <div id="app">
      <vue-multiselect
        track-by="name"
        label="name"
        v-model="value"
        :options="options"
        :multiple="true"
      ></vue-multiselect>
      <p>{{value}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then we’ll see the values that are selected in the p tag.

Async Selection

The options doesn’t have to be set synchronously. We can also populate it with data from an async source as in the following example:

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-multiselect@2.1.0"></script>
    <link
      rel="stylesheet"
      href="https://unpkg.com/vue-multiselect@2.1.0/dist/vue-multiselect.min.css"
    />
  </head>
  <body>
    <div id="app">
      <vue-multiselect
        track-by="name"
        label="name"
        v-model="value"
        :options="options"
        :multiple="true"
      ></vue-multiselect>
      <p>{{value}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

index.js:

Vue.component("vue-multiselect", window.VueMultiselect.default);

new Vue({
  el: "#app",
  data: {
    value: "",
    options: []
  },
  async beforeMount() {
    this.options = await Promise.resolve([
      { name: "Vue.js", language: "JavaScript" },
      { name: "Rails", language: "Ruby" },
      { name: "Sinatra", language: "Ruby" }
    ]);
  }
});

The code above populates the options from the resolved value of a promise, but we don’t have to make any changes to the template code.

Conclusion

The Vue-Multiselect lets us create a dropdown that works in various ways like single select, multiselect, and displaying a searchable drop down.

It can take options from synchronous and asynchronous sources.

Categories
JavaScript

New ES2020 Features — Strings, Numbers, and This

Since 2015, the JavaScript is evolving fast with lots of new features coming out in each iteration.

New versions of the JavaScript language specification has been updated almost yearly, with new language feature proposal being finalized faster than ever.

This means that new features are getting incorporated into modern browsers and other JavaScript run-time engines like Node.js at a pace that we haven’t seen before.

In 2019, there are many new features that are in the ‘Stage 3’ phase, which means that it’s very close to being finalized and browsers are getting support for these features now.

If we want to use them for production code, we can use something like Babel to transpile them to older versions of JavaScript so they can be used in older browsers like Internet Explorer if needed.

In this article, we’ll look at the matchAll method, numeric separators, and the globalThis object to get the global object in all environments.

String.matchAll()

In the current version of the JavaScript, there’s no string method that can be used to search for all occurrences of a string along with the capturing groups.

To search for all instances of a string, we have to write our own regular expression and use the match method to search for the substring we want to search for.

For example, we can use the match method with our own regular expression passed in to search for all instances of a string like with the following code:

const str = 'foo bar foo';  
const matches = str.match(/foo/g);  
console.log(matches);

When we run the code above, we should get:

[  
  "foo",  
  "foo"  
]

The match method gets us an array of all the matches of a string according to a regular expression. Note that we have the g flag at the end of the regular expression literal. Otherwise, only the first match will be returned. Capturing group matches aren’t included when then the g flag is included.

Now we have another method called matchAll which very similar to match .

However, it returns an iterator that’s not restartable so that we can loop through the iterator returned by the matchAll method to get all the matches that were found.

It also takes in a regular expression and converts any non-regular expression object to a regular expression with the RegExp constructor.

Since it returns an iterator, we can use the spread operator, for...of loop and the Array.from method with it.

Another difference from the match method is that capturing group results are returned from the matchAll method, but not the match method.

If we use match like in the following code:

const str = 'foo bar foo foobar foofoobarbarbar foobarbar';  
const matches = str.match(/foo(bar)+/g);

We get:

[  
  "foobar",  
  "foobarbarbar",  
  "foobarbar"  
]

If we use matchAll like in the following code instead:

const str = 'foo bar foo foobar foofoobarbarbar foobarbar';  
const matches2 = str.matchAll(/foo(bar)+/g);

We get:

[  
  [  
    "foobar",  
    "bar"  
  ],  
  [  
    "foobarbarbar",  
    "bar"  
  ],  
  [  
    "foobarbar",  
    "bar"  
  ]  
]

As we can see, the capturing group (bar) is returned with every result with matchAll, but it isn’t returned with match with the g flag in the regular expression.

Also, another important difference is that the iterator returned by the matchAll method isn’t restartable. Once the values returned from the iterator are exhausted, then we have to call the method again to create a new iterator if we want the results again.

Numeric Separators

In earlier versions of JavaScript, read long numbers is a pain because there’s nothing separating the numbers.

This means it’s almost impossible to tell how many digits are in big numbers from the human eye.

Now JavaScript has added digit separators for numbers to group them into smaller groups of digits with an underscore.

For example, we can use them as we do in the code below:

const x = 1_000_000_000_000

Then x would show 1000000000000 if we run console.log on it. It also works for decimal numbers:

const x = 1_363_372_373.378_473

Then x would show 1363372373.378473 if we run console.log on it. We don’t have to group digits in groups of 3. It can be grouped in whatever way we like, so if we have:

const x = 37378_44748

We get 3737844748 from the console.log of x if we run that. It also works for other bases like binary, hexadecimal or octal numbers. For example, we can write:

const binary = 0b111_111_111;  
const bytes = 0b1111111_11111111_1111111;  
const hex = 0xFAB_F00D;

The only catch is that we can’t use the underscore in number strings if we want to pass them into the Number or parseInt functions. If we write Number(‘123_456’) then we get NaN . If we run parseInt(‘123_456’) then we get 123. As we can see, they’re both incorrect. This means that we should sticky to number literals when using digit separators.

globalThis

The latest version of JavaScript also has the globalThis method. This lets us get the true global object of an application rather than relying on the this object, which may change according to the context that it’s in. For example, if we run the following code:

console.log(globalThis);
function foo() {  
  console.log(globalThis)  
}  
new foo();

We can see that both console.log output the window object, which is the object that has the things that are part of the browser tab.

If we console.log the this keyword instead of globalThis in the foo function if we create a new instance of foo with the new keyword, we wouldn’t get the window object, instead we would get the foo function.

For example, if we replaced globalThis with the this keyword like in the code below:

console.log(this);
function foo() {  
  console.log(this)  
}  
new foo();

We would get the window object in the first console.log , but the console.log inside the foo function would get us the foo constructor instead. Other environments like web or service workers, self would be a global object.

In Node.js, the global object is called global . globalThis works in all environments so that we can use it for accessing the true global object.

It works in the latest versions of many browsers and the latest version of Node.js.

It’s guaranteed to work in both window and non-window contexts. It works by referencing the Proxy around the actual global object which we can’t access directly.

Conclusion

In the current version of the JavaScript, there are no string methods that can be used to search for all occurrences of a string along with the capturing groups.

With the matchAll method, we can all the matches and get all the capturing groups. In earlier versions of JavaScript, read long numbers is a pain because there’s nothing separating the numbers.

This means it’s almost impossible to tell how many digits are in big numbers from the human eye.

Now JavaScript has added digit separators for numbers to group them into smaller groups of digits with an underscore.

We can group it however we like it with the separator. The latest version of JavaScript also has the globalThis method.

This lets us get the true global object of an application rather than relying on the this object, which may change according to the context that it’s in.

It also works in both window and non-window contexts so that run-time environments like Node.js can also use globalThis .

Categories
JavaScript TypeScript

Introduction to TypeScript Functions 

Functions are small blocks of code that takes in some inputs and may return some output or have side effects. A side effect is when a function modifies some variable outside the function. We need functions to organize code into small blocks that are reusable. Without functions, if we want to re-run a piece of code, we have to copy it in different places. Functions are critical to any TypeScript program. In this article, we look at how to define TypeScript functions, how to add types to functions, and passing in arguments in different ways.

Defining Functions

To define a function in TypeScript, we can do the following:

function add(a: number, b: number){  
  return a + b;  
}

The function above adds 2 numbers together and returns the sum. a and b are parameters, which lets us pass in arguments into the function to compute and combine them. add is the name of the function, which is optional in JavaScript. The return statement lets the result of the function be sent to another variable or as an argument of another function. This is not the only way to define a function. Another way is to write it as an arrow function:

const add = (a: number, b: number) => a + b;

The 2 are equivalent. However, if we have to manipulate this in the function, then the 2 aren’t the same since the arrow function will not change the value of this inside the function, like the function defined with the function keyword does. We can assign the function to a variable since functions are objects in TypeScript. Note that the function above only works if it’s one line since the return is implicitly done if the arrow function is one line. If it’s more than one line, then we write it with brackets like so:

const add = (a: number, b: number) => {  
  return a + b;  
}

This way we have to write the return statement explicitly. Otherwise, it won’t return a value.

In both examples above, we have the parameter and then the type following each parameter. This is because in TypeScript, if a parameter isn’t followed by a type, then the type will be inferred as having the any type, which will be rejected if we have the noImplicitAny flag set when we compile the code.

Function Types

To add more checks for the types of the parameters that are passed in and also the return type of the function, we can add a type designation to the function explicitly. We can do this by writing the signature and then adding the fat right arrow, and then appending the return type of the function after that. For example, we can write the following to designate the type of our add function:

const add: (a: number, b: number) => number =  
  (a: number, b: number) => a + b;

The type designation of the add function is:

(a: number, b: number) => number

in the code above. If we put different types for the parameter than the ones designated by the type we wrote, then the TypeScript compiler will raise an error and refuse to compile the code. For example, if we write:

const add: (a: number, b: number) => number =  
  (a: number, b: string) => a + b;

Then we get the error:

Type '(a: number, b: string) => string' is not assignable to type '(a: number, b: number) => number'.Types of parameters 'b' and 'b' are incompatible.Type 'number' is not assignable to type 'string'.(2322)

from the TypeScript compiler. If we don’t write out the type explicitly, TypeScript is still smart enough to infer the type from what’s given on the right side of the assignment operator, so we don’t have to write it out explicitly. It saves us effort from having to type everything to use TypeScript.

Calling a Function

If we are referencing and using a function, then we are calling a function.

To call the function, we write:

add(1, 2) // 3

Since our function returns a value, if we console log the return value of the add function:

console.log(add(1, 2)) // logs 3

We can also assign the return value to a variable:

const sum = add(1, 2);  
console.log(sum) // logs 3

Part of a Function

All functions have some or all of the following parts:

  • function keyword, which is optional
  • the function name, which is optional
  • parentheses — this is required for any function.
  • the parameter inside the parentheses, which is optional
  • opening and closing curly brackets — required for all functions except for single line arrow functions
  • return statement, which is optional. If a function doesn’t have a return statement, it returns undefined . A return statement which has nothing following it will end the executing of the function, so it’s handy for controlling the flow of your function.

Using Arguments

As we can see, many functions have arguments. Arguments are data that is passed into a function for computation, so if we have a function call add(1, 2), then 1 and 2 are the arguments.

On the other hand, parameters are what we write in the parentheses when we define a function to clarify what we can pass in as arguments.

We do not have to define a function parameters to pass in arguments since we have the arguments object in each function. However, it is not recommended since it’s unclear what you want to pass in. We can use the arguments for optional things that we want to pass in, however.

For example, if we go back to the add function we defined above:

function add(a: number, b: number){  
  return a + b;  
}

a and b are parameters. When we call it by writing add(1, 2). 1 and 2 are arguments.

We can specify up to 255 parameters when we define a function. However, we shouldn’t do define more than 5 usually since it becomes hard to read.

Pass in Arguments by Value

There are 2 ways to pass in arguments in TypeScript. One way is to pass in arguments by value. Passing by value means that the arguments passed in are completely separate from the variables you pass in.

The content of the variable is copied into the argument and is completely separate from the original variable if we pass in a variable to a function.

The original variable won’t change even if we change the argument variables that we passed in.

Primitive data types like string, number, boolean, undefined and the null object is passed in by value in TypeScript.

For instance, if we have the following code:

const a = 1;  
const addOne = (num: number) => num + 1  
const b = addOne(a);  
console.log(b);

a would still be 1 even after we call addOne on a since we make a copy of a and set it to num when we are passing in a as the argument for the call to addOne .

Pass in Arguments by Reference

Non-primitive are passed into a function by reference, which means that the reference to the object passed in as the argument is passed into the function. The copy of the content is not made for the argument and the passed in object is modified directly.

For example, if we have the following code:

let changeObj = (obj: { foo: string }) => obj.foo = 'bar'  
const obj = {  
  foo: 'baz'  
}  
changeObj(obj);  
console.log(obj); // logs {foo: "bar"}

The original obj object is defined as { foo: 'baz' }. However, when we pass obj into the changeObj function, where the passed in obj argument is changed in place. The original obj object that we passed in is changed. obj.foo becomes 'bar' instead of 'baz' as it’s originally defined.

Missing Arguments

You do not need to pass in all the arguments into a function in TypeScript. Whatever argument that’s not passed in will have undefined set in its place. So if you don’t pass in all the arguments, then you have to check for undefined in the arguments so that you don’t get unexpected results. For example, with the add function that we had:

function add(a, b){  
  return a + b;  
}

If we call add without the second argument by writing add(1), then we get NaN since 1 + undefined is not a number.

Default Function Parameter Values

We can set default function parameters for optional parameters to avoid unexpected results. With the add function, if we want to make b optional then we can set a default value to b. The best way to do it is:

function add(a: number, b: number = 1){  
  return a + b;  
}

In the code above, we set b to 1 so that we if we don’t pass in anything for b, we automatically get 1 by default for the b parameter. So if we run add(1) we get 2 instead of NaN .

An older, alternative way to do this is to check if the type of the parameter is undefined like so:

function add(a: number, b: number){  
  if (typeof b === 'undefined'){  
    b = 1;  
  }  
  return a + b;  
}

This achieves the same purpose as the first function, but the syntax is clumsier.

TypeScript functions allow us to organize code into small parts that can be reused. There’re many ways to define a function, but sticking to the commonly recommended ways like using arrow functions and not using arguments too much is recommended. We will continue to look at TypeScript functions in the next part of this series.

Categories
JavaScript TypeScript

Introduction to TypeScript Functions: Anonymous Functions and More

Functions are small blocks of code that take in some inputs and may return some output or have side effects. A side effect means that it modifies some variables outside the function.

We need functions to organize code into small blocks that are reusable.

Without functions, if we want to re-run a piece of code, we have to copy it in different places. Functions are critical to any TypeScript program.

In this article, we continue to look at different parts of TypeScript functions, including passing in a variable amount of arguments, recursion, function nesting, and defining functions in objects.

Calling Functions with More Arguments that Parameters

In TypeScript, we can call a function with more arguments than parameters. If we just pass them in without accessing them from the argument object, they’ll be ignored. You can get the extra arguments that aren’t in the parameters with the argument object and use them. The argument object has the parameters with numerical keys just like the indexes of an array. Another way to access extra arguments is through the rest parameter.

For example, if we call the add function with extra parameters:

function add(a: number, b: number, ...rest: any){  
  console.log(arguments);  
  return a + b;  
}  
add(1, 2, 3);

The ...rest part of the signature captures the parameters that we don’t expect to be passed in. We used the rest operator, which is indicated by the 3 periods before the word rest to indicate that there might be more parameters at the end after b. We need this in TypeScript so that we don’t get the mismatch between the number of parameters and the number of arguments passed in. In vanilla JavaScript, ...rest is optional.

In the console.log call, we should get:

0: 1  
1: 2  
2: 3

Variable Scope in Functions

Functions inside shouldn’t be accessible outside of functions unless they are global variables. We should avoid defining global variables as much as we can to prevent bugs and hard to trace errors since they can be accessed anywhere in the program. To prevent defining global variables, we should use let to define variables and const to define constants. For example, we should define functions like so:

function add(a: number, b: number){  
  let sum = a + b;  
  return sum;  
}

In this case, we have sum which is only accessible within the function since it’s defined with the let keyword.

Anonymous Functions

Anonymous are functions with no names. Since they have no name, they cannot be referenced anywhere. They are often passed into other functions as callback functions, which is called when the function is passed into an argument. However, you can assign anonymous functions into a variable so it becomes a named function.

They can also be self executing. This is means that you can define the function and make it run immediately. For example, if we write:

const sum = (function(a: number, b: number){  
  return a + b;  
})(1, 2);
console.log(sum) // log 3

We log 3 because we defined a function to add 2 numbers, and then passed in 1 and 2 as the arguments immediately after by wrapping the function in parenthesis and then passed the arguments to it.

Recursion

You can call the same function from within itself in TypeScript. This is called recursion. All recursive functions must have an end condition, which is called the base case, so that it knows when it stops executing. Otherwise, you can get a function that’s called an infinite number of times, which will crash the browser.

To write a recursive function, we can write:

function sumOfSquares(num: number): number {  
  let sum: number = Math.pow(num, 2);  
  if (num == 1) {  
    return 1  
  } else {  
    return sum + sumOfSquares(num - 1)  
  }    
}

In this example, we wrote a function to compute the sum of squares for a given number. We compute the square of num and then if we have num equal to 1 then we return 1. Otherwise, we return the sum of sum plus the result of call sumOfSquares on num — 1 . We keep reducing num so that we can reach our base case of 1, adding up the results while doing so.

Nesting Functions

Functions can be nested within each other. This means that we can define a function inside another function. For example, we can write:

function convertToChicken(name: string){  
  function getChickenName(name: string){  
    return `Chicken ${name}`;  
  }  
  return getChickenName(name)  
}

In this case, we called getChickeName inside the convertToChicken call. So if we write convertToChicken('chicken') , then we get 'Chicken chicken' since we called get getChickeName and returned the result. The scope of variables is the name. let and const are block-scoped so they cannot be accessed outside of the original function that’s defined, but they are available in the nested function, so if we have:

function convertToChicken(name: string) {  
  let originalName = name;  function getChickenName(newName: string) {  
    console.log(originalName)  
    return `Chicken ${newName}`;  
  }  
  return getChickenName(name)  
}

Then originalName will still be defined in the console.log.

Defining Function in an Object

We can define a function in an object in a few ways. We can use the function keyword or arrow function as usual, but we can also write it with a shorthand for the function keyword. For example, if we have a bird object and we want to define the chirp function, we can write:

const bird = {  
 chirp: function(){  
   console.log('chirp', this)  
  }  
}

or use the following shorthand:

const bird = {  
 chirp(){  
   console.log('chirp', this)  
  }  
}

The 2 are the same since the chirp function will have the bird object as the value of this.

On the other hand, if you use an arrow function:

const bird = {  
 chirp: () => {  
   console.log('chirp', this)  
  }  
}

We’ll get an error from the Typescript compiler because the value of this is the globalThis value, which the TypeScript compiler doesn’t allow. We get the error “The containing arrow function captures the global value of ‘this’.(7041)” when we try to compile the code above.

TypeScript functions allow us to organize code into small parts that can be reused. There’re many ways to define a function, but sticking to the commonly recommended ways like using arrow functions and not using arguments too much is recommended.

Categories
JavaScript React

Use React to Display Images in a Grid Like Google and Flickr

If you use image search websites like Google Image Search or Flickr, you will notice that their images display in a grid that looks like a wall of bricks. The images are uneven in height, but equal in width. This is called the masonry effect because it looks like a wall of bricks.

To implement the masonry effect, we have to set the width of the image proportional to the screen width and set the image height to be proportional to the aspect ratio of the image.

This is a pain to do if it’s done without any libraries, so people have made libraries to create this effect.

In this article, we will build a photo app that allows users to search for images and display images in a masonry grid. The image grid will have infinite scroll to get more images. We will build it with React and the React Masonry Component library. For infinite scrolling, we will use the React Infinite Scroller library. We will wrap the React Infinite Scroller outside the React Masonry Component to get infinite scrolling with the masonry effect when displaying images.

Our app will display images from the Pixabay API. You can view the API documentation and register for a key at https://pixabay.com/api/docs/

To start, we run Create React App to create the app. Run npx create-react-app photo-app to create the initial code for the app.

Then we install our own libraries. We need React Infinite Scroller, React Masonry Component, Bootstrap for styling, Axios for making HTTP requests, Formik and Yup for form value data binding and form validation, and React Router for routing URLs to our pages.

To install all the packages, run:

npm i axios bootstrap formik react-bootstrap react-infinite-scroller react-masonry-component react-router-dom yup

to install all the packages.

With all the packages installed, we can start building the app. First start with replacing the code in App.js with:

import React from "react";  
import { Router, Route } from "react-router-dom";  
import HomePage from "./HomePage";  
import { createBrowserHistory as createHistory } from "history";  
import TopBar from "./TopBar";  
import ImageSearchPage from "./ImageSearchPage";  
import "./App.css";  
const history = createHistory();

function App() {  
  return (  
    <div className="App">  
      <Router history={history}>  
        <TopBar />  
        <Route path="/" exact component={HomePage} />  
        <Route path="/imagesearch" exact component={ImageSearchPage} />  
      </Router>  
    </div>  
  );  
}

export default App;

to add the top bar and the routes for our app into the entry point of the app.

Next remove all the code in App.css and add:

.page {  
  padding: 20px;  
}

to add padding to our pages.

Then we set our React Masonry Component options by creating a exports.js in the src folder and add:

export const masonryOptions = {  
  fitWidth: true,  
  columnWidth: 300,  
  gutter: 5  
};

These options are very important. We need to set fitWidth to true to center our grid. columnWidth must be a number to get constant width. It will scale according to screen size only with constant width. The gutter value is the margin between items.

The full list of options are at https://masonry.desandro.com/options.html

Next we create our app’s home page by creating HomePage.js in the src folder and add:

import React from "react";  
import { getImages } from "./request";  
import InfiniteScroll from "react-infinite-scroller";  
import Masonry from "react-masonry-component";  
import "./HomePage.css";  
import { masonryOptions } from "./exports";

function HomePage() {  
  const [images, setImages] = React.useState([]);  
  const [page, setPage] = React.useState(1);  
  const [total, setTotal] = React.useState(0);  
  const [initialized, setInitialized] = React.useState(false); 
  const getAllImages = async (pg = 1) => {  
    const response = await getImages(page);  
    let imgs = images.concat(response.data.hits);  
    setImages(imgs);  
    setTotal(response.data.total);  
    pg++;  
    setPage(pg);  
  }; 

  React.useEffect(() => {  
    if (!initialized) {  
      getAllImages();  
      setInitialized(true);  
    }  
  }); 

  return (  
    <div className="page">  
      <h1 className="text-center">Home</h1>  
      <InfiniteScroll  
        pageStart={1}  
        loadMore={getAllImages}  
        hasMore={total > images.length}  
      >  
        <Masonry  
          className={"grid"}  
          elementType={"div"}  
          options={masonryOptions}  
          disableImagesLoaded={false}  
          updateOnEachImageLoad={false}  
        >  
          {images.map((img, i) => {  
            return (  
              <div key={i}>  
                <img src={img.previewURL} style={{ width: 300 }} />  
              </div>  
            );  
          })}  
        </Masonry>  
      </InfiniteScroll>  
    </div>  
  );  
}  
export default HomePage;

In the home page, we just get the images when the page loads. When the user scroll down, we load more images by adding 1 to the currentpage value and get the image with the page number.

With the InfiniteScroll component, which is provided by React Infinite Scroll, wrapped outside the Masonry component, which is provided by React Masonry Component, we display our images in a grid, and also display more when the user scrolls down until the length of the images array is greater than or equal to the total, which is from the total field given by the Pixabay API’s results.

We load images when the page loads by checking if initialized flag is true, we only load images on page load if initialized is false and the when the request is first made to the API and succeeds, then we set initialized flag to true to stop requests from being made on every render.

Next we create a image search page by creating the ImageSearchPage.js file and adding the following:

import React from "react";  
import { Formik } from "formik";  
import Form from "react-bootstrap/Form";  
import Col from "react-bootstrap/Col";  
import Button from "react-bootstrap/Button";  
import * as yup from "yup";  
import InfiniteScroll from "react-infinite-scroller";  
import Masonry from "react-masonry-component";  
import { masonryOptions } from "./exports";  
import { searchImages } from "./request";

const schema = yup.object({  
  keyword: yup.string().required("Keyword is required")  
});

function ImageSearchPage() {  
  const [images, setImages] = React.useState([]);  
  const [keyword, setKeyword] = React.useState([]);  
  const [page, setPage] = React.useState(1);  
  const [total, setTotal] = React.useState(0);  
  const [searching, setSearching] = React.useState(false); 
  
  const handleSubmit = async evt => {  
    const isValid = await schema.validate(evt);  
    if (!isValid) {  
      return;  
    }  
    setKeyword(evt.keyword);  
    searchAllImages(evt.keyword, 1);  
  }; 

  const searchAllImages = async (keyword, pg = 1) => {  
    setSearching(true); 
    const response = await searchImages(keyword, page);  
    let imgs = response.data.hits;  
    setImages(imgs);  
    setTotal(response.data.total);  
    setPage(pg);  
  }; 

  const getMoreImages = async () => {  
    let pg = page;  
    pg++;  
    const response = await searchImages(keyword, pg);  
    const imgs = images.concat(response.data.hits);  
    setImages(imgs);  
    setTotal(response.data.total);  
    setPage(pg);  
  }; 

  React.useEffect(() => {}); 
  
  return (  
    <div className="page">  
      <h1 className="text-center">Search</h1>  
      <Formik validationSchema={schema} onSubmit={handleSubmit}>  
        {({  
          handleSubmit,  
          handleChange,  
          handleBlur,  
          values,  
          touched,  
          isInvalid,  
          errors  
        }) => (  
          <Form noValidate onSubmit={handleSubmit}>  
            <Form.Row>  
              <Form.Group as={Col} md="12" controlId="keyword">  
                <Form.Label>Keyword</Form.Label>  
                <Form.Control  
                  type="text"  
                  name="keyword"  
                  placeholder="Keyword"  
                  value={values.keyword || ""}  
                  onChange={handleChange}  
                  isInvalid={touched.keyword && errors.keyword}  
                />  
                <Form.Control.Feedback type="invalid">  
                  {errors.keyword}  
                </Form.Control.Feedback>  
              </Form.Group>  
            </Form.Row>  
            <Button type="submit" style={{ marginRight: "10px" }}>  
              Search  
            </Button>  
          </Form>  
        )}  
      </Formik>  
      <br />  
      <InfiniteScroll  
        pageStart={1}  
        loadMore={getMoreImages}  
        hasMore={searching && total > images.length}  
      >  
        <Masonry  
          className={"grid"}  
          elementType={"div"}  
          options={masonryOptions}  
          disableImagesLoaded={false}  
          updateOnEachImageLoad={false}  
        >  
          {images.map((img, i) => {  
            return (  
              <div key={i}>  
                <img src={img.previewURL} style={{ width: 300 }} />  
              </div>  
            );  
          })}  
        </Masonry>  
      </InfiniteScroll>  
    </div>  
  );  
}  
export default ImageSearchPage;

We do not load images on the first load on this page. Instead, the user enters a search term in the form and when the user clicks the Search button, then handleSubmit is called. The evt object has the form values, which is updated by the Formik component. Yup provides the form validation object with the schema object, we just check if keyword is required.

In the handlesubmit function, we get the evt object, which we validate against the schema by callingschema.validate, which returns a promise. If the promise returns to something truthy, then we proceed with making the request to the Pixabay API with the search keyword and page number.

We have the same setup as the home page for the infinite scroll and masonry effect image grid. The only difference is that we call the searchAllImages function which has similar logic as the getAllImages function, except that we pass in the keyword parameter in addition to the page parameter. We set the imgs variable to the array returned from the Pixabay API and set the images by calling setImages. We also set the page by calling setPage.

When the user scrolls far enough down that content runs out, the getMoreImages function is called when images.length is less than the total. The total is set by getting the total field from the API.

We use masonryOptions from exports.js just like in the home page and display images the same way.

Next create request.js in the src folder to add the code for making HTTP requests to the back end, like so:

const axios = require("axios");  
const APIURL = "https://pixabay.com/api";

export const getImages = (page = 1) =>  
  axios.get(`${APIURL}/?page=${page}&key=${process.env.REACT_APP_APIKEY}`);

export const searchImages = (keyword, page = 1) =>  
  axios.get(  
    `${APIURL}/?page=${page}&key=${process.env.REACT_APP_APIKEY}&q=${keyword}`  
  );

We have the getImages for just getting images and searchImages that also sends the search term to the API. process.env.REACT_APP_APIKEY is from setting the REACT_APP_APIKEY variable in the .env file in the project’s root folder.

Next create TopBar.js in the src folder and add:

import React from "react";  
import Navbar from "react-bootstrap/Navbar";  
import Nav from "react-bootstrap/Nav";  
import { withRouter } from "react-router-dom";

function TopBar({ location }) {  
  React.useEffect(() => {}); 

  return (  
    <Navbar bg="primary" expand="lg" variant="dark">  
      <Navbar.Brand href="#home">Photo App</Navbar.Brand>  
      <Navbar.Toggle aria-controls="basic-navbar-nav" />  
      <Navbar.Collapse id="basic-navbar-nav">  
        <Nav className="mr-auto">  
          <Nav.Link href="/" active={location.pathname == "/"}>  
            Home  
          </Nav.Link>  
          <Nav.Link  
            href="/imagesearch"  
            active={location.pathname == "/imagesearch"}  
          >  
            Search  
          </Nav.Link>  
        </Nav>  
      </Navbar.Collapse>  
    </Navbar>  
  );  
}  

export default withRouter(TopBar);

This contains the React Bootstrap Navbar to show a top bar with a link to the home page and the name of the app. We check the location.pathname to highlight the right links by setting the active prop, where the location prop is provided by React Router by wrapping the withRouter function outside the TopBar component.

Finally, in index.js , we replace the existing code with:

<!DOCTYPE html>  
<html lang="en">  
  <head>  
    <meta charset="utf-8" />  
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />  
    <meta name="viewport" content="width=device-width, initial-scale=1" />  
    <meta name="theme-color" content="#000000" />  
    <meta  
      name="description"  
      content="Web site created using create-react-app"  
    />  
    <link rel="apple-touch-icon" href="logo192.png" />  
    <!--  
      manifest.json provides metadata used when your web app is installed on a  
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 
    -->  
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />  
    <!--  
      Notice the use of %PUBLIC_URL% in the tags above.  
      It will be replaced with the URL of the `public` folder during the build.  
      Only files inside the `public` folder can be referenced from the HTML.Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC\_URL%/favicon.ico" will  
      work correctly both with client-side routing and a non-root public URL.  
      Learn how to configure a non-root public URL by running `npm run build`.  
    -->  
    <title>Photo App</title>  
    <link  
      rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"  
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"  
      crossorigin="anonymous"  
    />  
  </head>  
  <body>  
    <noscript>You need to enable JavaScript to run this app.</noscript>  
    <div id="root"></div>  
    <!--  
      This HTML file is a template.  
      If you open it directly in the browser, you will see an empty page. You can add webfonts, meta tags, or analytics to this file.  
      The build step will place the bundled scripts into the <body> tag. To begin the development, run `npm start` or `yarn start`.  
      To create a production bundle, use `npm run build` or `yarn build`.  
    -->  
  </body>  
</html>

to add the Bootstrap CSS and change the title.