Coding the Add Books Functionality

Adding books

Books are added to the books collection through Meteor Methods called from the client. The functionality that adds books is an admin-only function. The admin enters details about the book through the user interface. The authors of the entered books are searched using the firstname of an author. A list of authors is published to the client from the server.

The added book details are saved into the book collection. The saved book’s _id is returned from the server. This _id is passed to the component used to upload files. The upload files component uploads the pdf and cover image of the book to the server, where they’re handled.

Creating an index for the author’s first name

In the user interface, we want to present the admin with the ability to search for the author of a book using the first name of the author. When searching for a piece of data, MongoDB performs a complete scan of all records in a collection, if the collection isn’t indexed.

An index is like a lookup page that the database can query to retrieve the information instead of scanning all the records one at a time. It functions like the index page of a book, where it’s easy to look up where specific information in the book is located.

Scroll down and open the imports/api/authors/authors.js file in the coding playground at the end of this section. On line 20, an index is created for the firstname property of the author collection. Creating an index is done on the server, hence the method is wrapped in a Meteor.isServer check. The 1 in the AuthorsCollection.createIndex({firstname: 1}) call means that the index is stored in ascending order. If we want the index saved in descending order, -1 is used.

 AuthorsCollection.createIndex({firstname: 1});

Author search data publication

Open the imports/api/authors/server/publication.js file at the bottom of this page. The publication is named authors.findAuthor, and this is the name that the client subscribes to. The publication takes a parameter that we call search. A check is performed on the search input parameter to ascertain if it belongs to one of the following—String, undefined, and null.

On line 7, a regular expression is created using RegExp. The i flag passed to RegExp denotes that the search term is case insensitive. The author collection is searched to find the records that meet the regular expression that has been passed in.

Author search subscription

Open the imports/ui/addBook.jsx file. On the textbox defined in line 146–152, an author record is found using the firstname property of the author collection. Typing in the textbox triggers the handleTextInputChange function on line 38. This updates the authorFirstName state, causing the component to rerender.

On line 23, the useTracker function is defined and an async function is passed to it. The authorFirstName is on the dependency array. If the variable in the dependency array changes, the useTracker function is rerun. A subscription inside the useTracker function is made to the authors.findAuthor publication, passing as a parameter the value of the text input box, which is saved inside the authorFirstName state.

const handle = Meteor.subscribe("authors.findAuthor", authorFirstName);

Next, we set the loading state with the value of !handle.ready() on line 25. A regular expression is created on line 26 that turns the search term into a regular expression that’s case-insensitive.

On line 27, there’s an if statement that checks if the subscription is ready and also checks if the length of the text in the input textbox is greater than 2. If all of the if statement checks pass, we await the result of the call that we made to the searchFirstName function.

The searchFirstName accepts the collection as the first input parameter and the search term as the second input parameter. The function returns a JavaScript Promise that resolves after 100 milliseconds using setTimeout. The setTimeout function calls its callback at the end of the time duration. The example below will resolve the Promise with the value returned by collection.find().

setTimeout(() => {
  resolve(collection.find({ firstname: 
  searchTerm }).fetch());
 }, 100)

The await keyword is used to wait for the result of a Promise as a Promise is an asynchronous function that does not return its value immediately. Using await paused further execution until the Promise is fulfilled.

We await the return value of searchFirstName and pass the value of the function to setAuthors so as to set the React state named authors.

On line 161 the authors state which is an array is looped through to render a paragraph of authors that match the search term. If the search produces no result “No data” is shown on a </p> tag. Take a look at lines 175-177. If the length of the search term which is stored in the authorFirstName state is greater than 2 and the authors array length is 0, it means the search query does not bring back any data, so “No data” is displayed.

On line 166, we have defined a click handler so that the user can select a particular author by clicking on the corresponding paragraph.

{ 
 authorFirstName.length > 2 && 
  authors.length === 0 && (
   <p className="lead text-primary">No 
    data
   </p>
 )
}

The selected author is saved in a state called selectedAuthors, which is an array. The selectedAuthors state on line 183 is looped over, and the author is rendered in a paragraph. We have the ability to remove the selection by clicking the “X,” which calls the removeSelectedAuthor function.

The submitBookDetails function

The submitBookDetails function defined on line 56 is attached to the submit event in the form. The function gathers all values from the state of the input textbox in the form. The default action of the form is prevented by calling e.preventDefault(). This prevents the form from submitting and reloading the web page.

On line 59, ​​we loop over the selectedAuthors state and return an array of author objects, each with an authorId and a name property.

let authors = selectedAuthors.map((a) => 
 {
   let author = {
     authorId: a._id,
     name: `${a.firstname}${a.lastname}`,
    };
     return author;
    }
);

The returned array from above contains an object with keys of authorId and name in accordance with the object defined in imports/api/books/books.js.

The Meteor Method, books.insertNewBook, which has a corresponding method defined at the server (imports/api/books/methods.js), is called on line 88. This returns the bookId if the call was a success. The state of all the text input is cleared to its default value.

Task

Run the application, and create a book detail from the UI. A list of three authors is added to the author collection of the server/main.js file if no author data already exists during application startup. Log in as an admin user with the username (“admin”) and the password (“password”). Search using the firstname of any of the authors in server/main.js and save a new book detail.

Get hands-on with 1400+ tech skills courses.