DEV Community

Naftali Kulik
Naftali Kulik

Posted on • Updated on

Book Wyrms 2.0: "React"ing to my First Project

When I started working on my first big project, my goal was to hit it out of the park. I wanted to put all of my (admittedly limited) knowledge to use and build something I could be proud of. The result was 750 lines of vanilla JavaScript code that, while it worked beautifully, would be quite a challenging task to read for anyone other than me, who wrote it (and we'll see if even I can read it after I have a year or two to forget about it). Don't get me wrong, I am quite proud of the effort. However, even at the time, I was well aware that there was probably a better way to do it. My goal was to put the knowledge that I had to the test, and I did.

Enter React. Little did I know at the time, my project was a tailor-made React application, only without React. Let's take a look at some of the code and see how React could (and in fact did) allow me to completely refactor the code to make a much more organized application, while perhaps even adding or improving functionality. For obvious reasons, I will not be going through all 750 lines of code here, this is a blog, not a book. It's here if you want to take a crack at it yourself.

Let's quickly walk through the functionality of the app before we continue (or you can watch this walkthrough video). The app is essentially a knockoff of Goodreads. The user can search books by title or author (book information obtained via the Open Library API), and view detailed information about the book when a search result item is selected. Users can also log in or create an account, allowing them to rate and review books, as well as add books to a read list or wish list. Here's a screenshot of the app in action:

Image description

As you can see from the picture, much of the app looks like it could be made up of React components. The first and perhaps biggest advantage of using React would be the ability to use JSX so that something like this:

const ul = document.createElement('ul')
    ul.id = id
    document.getElementById(divId).appendChild(ul)
    books.map(book => {
        let rating
        const cover = book.cover.substring(0, book.cover.length - 5) + 'S.jpg'
        book.ownRating === 'none' ? rating = 'You have not yet rated this book' : rating = `You have given this book a rating of ${book.ownRating} out of 5`
        const li = document.createElement('li')
        li.className = `li-for-${id}`
        const bookCover = document.createElement('img')
        bookCover.src = `${cover}`
        li.appendChild(bookCover)
        const h4 = document.createElement('h4')
        h4.textContent = `${book.title}`
        li.appendChild(h4)
        li.appendChild(document.createElement('br'))
        if (id === 'readList') {
            const bookRating = document.createElement('p')
            bookRating.textContent = `${rating}`
            li.appendChild(bookRating)
            let review
            book.review === 'none' ? review = '<p>You have not reviewed this book</p>' : review = `<h5>Your Review:</h5><p>${book.review}</p><br><button id="delete-review-${book.id}">Delete this review</button>`
            const bookReview = document.createElement('p')
            bookReview.innerHTML = review
            bookReview.className = 'user-review'
            li.appendChild(bookReview)
        }
        if (id === 'wishList') {
            const addToRead = document.createElement('button')
            addToRead.id = `make-read-book-${book.id}`
            addToRead.textContent = 'Add this book to your read list'
            li.appendChild(addToRead)
            addToRead.addEventListener('click', () => {
                currentUser.wishList.splice(currentUser.wishList.indexOf(book), 1)
                currentUser.readList.push(book)
                updateBookFromUserEnd('wantToRead', book.id, currentUser.username, true)
                handlePostPatch('users', 'PATCH', currentUser, updateUserCallback)
            })
            const removeBook = document.createElement('button')
            removeBook.id = `remove-book-${book.id}`
            removeBook.textContent = 'Remove this book'
            removeBook.style.margin = '3px'
            li.appendChild(removeBook)
            removeBook.addEventListener('click', () => {
                currentUser.wishList.splice(currentUser.wishList.indexOf(book), 1)
                updateBookFromUserEnd('wantToRead', book.id, currentUser.username, false)
                handlePostPatch('users', 'PATCH', currentUser, updateUserCallback)
            })
Enter fullscreen mode Exit fullscreen mode

can be drastically simplified by using JSX to simply write out the HTML syntax in your React component. This alone would've slashed the amount of code by half and made it significantly more readable.

However, it still would have been a mostly disorganized jumble of different functions responsible for accomplishing different tasks. Dividing these into separate files, or components can go a long way toward organizing and simplifying the code. Let's take a look at some of the functions from the code:

renderPageButton(pages, id, list)

renderSearchResultPages(i, id)

renderBookResults(book)

renderDetailedBook()

renderBasicUserInfo(user)

renderUserLists(books, id, divId)
Enter fullscreen mode Exit fullscreen mode

Hmmm. Those look like they have much in common with React components! Each of those functions is responsible for rendering DOM nodes, and they even take arguments that are similar to props in that they allow the same function to be used for several parts of the same document by passing different information down to the function. One can easily imagine how this code:

bookList.map(book => renderBookResult(book))
Enter fullscreen mode Exit fullscreen mode

can get lost somewhere in a 750-line code jungle. A separate file (component) can make this easy to read and easy to find if necessary. For example:

import React from 'react'
import BookCard from './BookCard'

function BookList({ books }) {
    return (
        <ul>
            {books.map(book => <BookCard book={book} key={book.id} />)}
        </ul>
    )
}

export default BookList
Enter fullscreen mode Exit fullscreen mode

Now the BookList component lives in its own file and can be imported wherever it's needed, with the appropriate list passed as props. The imported BookCard component probably looks something like this:

import React from 'react';
import SearchResultBtn from './SearchResultBtn';

function BookCard({ book }) {

    return (
        <li className='book-card'>
            <img src={book.cover} alt={book.title} />
            <h5>{book.title}</h5>
            <p>By {book.author}</p>
            <SearchResultBtn />
        </li>
    )
}

export default BookCard
Enter fullscreen mode Exit fullscreen mode

and is generic enough to be used for many different book cards.

There's one more feature of React that seems like it was made for this app, and that is state. Take the following code, copied from the Book Wyrm code:

let currentBook;

let currentUser;

function updateBook(book) {
    currentBook = book
    renderDetailedBook()
    return currentBook
}

function updateUser(user) {
    currentUser = user
    renderBasicUserInfo(currentUser)
    return currentUser
}
Enter fullscreen mode Exit fullscreen mode

Let's run through what state does for a moment. State allows you to save information to a variable, and update that variable by passing the new value to a setter function. Additionally, the component will re-render every time the state variable is modified. Now take a look at the above code again. Isn't that what we're doing? The currentBook and currentUser variables are declared but not assigned, and the updateBook and updateUser functions assign whatever value is passed as an argument to the appropriate variable and re-render the appropriate DOM element to reflect the changes. Using state, we can write this:

import React, { useState } from 'react';

const [currentBook, setBook] = useState({});

const [currentUser, setUser] = useState({});
Enter fullscreen mode Exit fullscreen mode

to accomplish the same thing. These variables and setter functions can then be passed down to whichever components need them via props, or, as I did in my own refactor, used with context.

In conclusion, one can easily see how not only was I able to completely refactor my code in a way that made it (mostly) more readable and organized, I was actually able to add functionality without sacrificing clarity, and even used react-router-dom (a topic for a different time perhaps) to make it appear as if the application consisted of several pages and to seamlessly navigate between them. I have deployed both the first version and the second version, which, if you compare the two shows that not only can React help you make your code more easily readable and organized, it (with a little help from React Bootstrap!) can also help drastically improve the functionality of your app, as well as the user experience.

Top comments (0)