React Hooks: A Clean Approach to State Management

Feb 2nd, 2021


React JS Icon pngitem

Single Page Web Apps

Typical Web Apps

You could call just about any website you navigate to on your browser a web app. In essence, a web app is an application, hosted on a server, that your web browser can download and run.

With a typical website, a server host some html web pages, that may contain some content(or Javascript perhaps wink wink), that the browser downloads, runs and renders in into what you actually see and interact with.

Why Single Page?

You probably noticed when interacting with typical web pages, that using with them requires many page reloads, enter the single page web app.

With a single page web app, the page only loads once, when you first navigate to the page. The page content then dynamically changes as you interact with the application.

Page refreshes are expensive operations, that can take seconds of time with each click of the mouse. Naturally, in my quest to develop easy to use business software, that allowed users to maximize productivity, it wasn't long before I started looking into single page web apps.

Over the years, there have been many technologies developed to accomplish this goal. You probably recent heard heard of flash being ended. Today we are going to be focusing on a popular framework used by some of the biggest names in tech.

Enter Javascript

Javascript is a loosely typed high level programming language, that is run almost(excepting Node.js) exclusively in web browsers. Javascript can be developed in many fashions, including Object-Oriented and Functional programming styles.

I will continue this article using a functional style for our js examples, because Javascript is very well suited for this.

You notice that I used the phrase 'loosely typed' when describing Javascript, this is in fact one of the best, and worst, features of Javascript. Being loosely typed gives Javascript an almost magical truly dynamic feel, but this can also lead to very strange errors and behaviors if you are not careful. That being said, it is still true that Javascript has always been my go-to tool of choice for building dynamic web pages.

React js

React js is a Javascript framework developed by a software engineer at Facebook. In fact, the Facebook web app is written in React. React deals with UIs in terms of components, and is heavy focused on application state(being all the data your application uses in memory, including all variables). Changes in application state cause the UI components related to that state to be individually re-drawn.

Before the introduction of hooks, React components had to be built as clunky class based components, with tedious and complicated lifecycle management.

Hooks

With hooks, we can build functional components, those are components that are actually functions, instead of what would typically be objects.

Hooks allow us to really focus on the scope of application state, exposing data only to the components that need it. They hook into the components lifecycle, and abstract the details of re-rendering components.

Book Inventory Single Page Web App

As is typical here, our example will focus an app to keep track of your personal book library. Keep in mind, this is a simple application to demonstrate good state management. There is no API backing this app, and none of the data will persist, the app will reset every time the page is reloaded.

The project repository can be found on Github

The Data Schema

Our app will list books that exist in your library, it will also allow you to add books to your inventory.

Book

Our book object will hold some information about each book.

in JSON:


book: {
  title: "a string",
  author: "another string",
  isbn: "yet another string"
}

Application State

Our application state will simply be a list of books, that is the books we have added to our library.

The Book Inventory Page Component

Let's create a component to contain our app, we will call it BookInventoryPage, and put it in a file called 'book-inventory-page.js'


import React from 'react';

const BookInventoryPage = ({}) => {
  
  return(
    <>
      <h1>
        My Book Library
      </h1>
    </>
  );
}

export default BookInventoryPage;

We import React in order to signify that BookInventoryPage is a react component. As of right now, the component will render a lonely header 1 'My Book Library'. Using 'export default BookInventoryPage' allows us to import this component in our 'App.js' driver file.


import './App.css';
import BookInventoryPage from "./components/book-inventory-page";

function App() {
  return (
    <div className="App">
      <BookInventoryPage />
    </div>
  );
}

export default App;

screen shot of app so far

The useState Hook

Since the list of books in our library, our application state, will determine what we should see on the page, we need a way to manage the components lifecycle, and redraw the components affected when we add books to our library.

The useState hook will listen for changes to the state of the book list, and redraw components when it changes. The useState hook will create an array of two objects:

  • The actual value of data variable
  • A setter function for the variable

We will need to import useState from React, and declare our hook by calling the useState method with an empty array, since the data will be a collection that is initially empty.


import React, { useState } from 'react';

const BookInventoryPage = ({}) => {

  const [bookList, setBookList] = useState([]);
  
  return(
    <>
      <h1>
        My Book Library
      </h1>
    </>
  );
}

export default BookInventoryPage;

Book List Component

Now we will create a component to render our book list. We will create it in a file called 'book-list-component.js'


import React from 'react';

const BookListComponent = ({bookList}) => {

  return(
    <div>
       Books go here
    </div>
  );
}

export default BookListComponent;

You might notice that our functional component requires a bookList argument, we will inject our hook here.

We will use the map function to render each book in the list. The map function allows us to iterate over and transform a collection of objects.

But first we need a component to render a book. We will place this component in 'book-list-component.js' between the export statement and the BookListComponent


const BookComponent = ({book, index}) => {

    return (
        <div className={"book-container"}>
            <div>
                Book #{index + 1}
            </div>
            <div>
                Title: {book.title}
            </divdiv>
            <div>
                Author: {book.author}
            </div>
            <div>
                ISBN: {book.isbn}
            </div>
        </div>
    );
}

The BookComponent will take an arguments of book and index, and with a little css styling, render a row for each book.

We will use flex box to style the parent div in row format. in 'index.css' add this code for the book-container class


.book-container {
  display: flex;
  flex-direction: row;
}

Finally we will use a map function to render the book list


import React from 'react';

const BookListComponent = ({bookList}) => {

    return(
        <div>
            <div>
                My Books
            </div>
            {bookList.map((book, index) => {
                return <BookComponent book={book} index={index}/>
            })}
        </div>
    );
}

const BookComponent = ({book, index}) => {

    return (
        <div className={"book-container"}>
            <div>
                Book #{index + 1}
            </div>
            <div>
                Title: {book.title}
            </div>
            <div>
                Author: {book.author}
            </div>
            <div>
                ISBN: {book.isbn}
            </div>
        </div>
    );
}

export default BookListComponent;

It is important to notice that we wrap all Javascript in the return method in braces({, })

Next, we will add the BookListComponent to the book page


import React, { useState } from 'react';
import BookListComponent from "./book-list-component";

const BookInventoryPage = ({}) => {

    const [bookList, setBookList] = useState([]);

    return(
        <>
            <h1>
                My Book Library
            </h1>
            <BookListComponent bookList={bookList}/>
        </>
    );
}

export default BookInventoryPage;

screen shot of app so far

Add Book Component

So in theory, we can now render the book list, but we still need a way to add books.

We will create AddBookComponent functional component in 'add-book-component.js'


import React from 'react';

const AddBookComponent = ({bookList, setBookList}) => {
    
    return (
        <div>
            Add books here
        </div>
    );
}

export default AddBookComponent;

Notice this component requires an argument of setBookList, you might of guessed we will use the setter function from our book list hook.

Next we will add inputs to our component


import React, {useState} from 'react';

const AddBookComponent = ({setBookList}) => {

    const [title, setTitle] = useState('');
    const [author, setAuthor] = useState('');
    const [isbn, setIsbn] = useState('');

    return (
        <div className={"add-book-container"}>
            <div className={"book-container"}>
                <div>
                    Title:
                </div>
                <input onChange={event => {
                    setTitle(event.target.value);
                }}
                       value={title}
                />
            </div>

            <div className={"book-container"}>
                <div>
                    Author:
                </div>
                <input onChange={event => {
                    setAuthor(event.target.value);
                }}
                       value={author}
                />
            </div>

            <div className={"book-container"}>
                <div>
                    ISBN:
                </div>
                <input onChange={event => {
                    setIsbn(event.target.value);
                }}
                       value={isbn}
                />
            </div>
        </div>
    );
}

export default AddBookComponent;

Notice that we introduced a useState hook for each field. This allows the screen to update every time a character is typed into the input box. We also reused the book-container css class in order to position the input boxes next to their label text.

Custom Hook

This provides us with an opportunity to create a custom hook. We will combine all three of these hooks into one hook component.


import React, {useState} from 'react';

const AddBookComponent = ({setBookList}) => {

    const [
        title,
        setTitle,
        author,
        setAuthor,
        isbn,
        setIsbn
    ] = AddBookInputHook();

    return (
        <div className={"page-container"}>
            <div className={"book-container"}>
                <div>
                    Title:
                </div>
                <input onChange={event => {
                    setTitle(event.target.value);
                }}
                       value={title}
                />
            </div>

            <div className={"book-container"}>
                <div>
                    Author:
                </div>
                <input onChange={event => {
                    setAuthor(event.target.value);
                }}
                       value={author}
                />
            </div>

            <div className={"book-container"}>
                <div>
                    ISBN:
                </div>
                <input onChange={event => {
                    setIsbn(event.target.value);
                }}
                       value={isbn}
                />
            </div>
        </div>
    );
}

const AddBookInputHook = () => {

    const [title, setTitle] = useState('');
    const [author, setAuthor] = useState('');
    const [isbn, setIsbn] = useState('');

    return [
        title,
        setTitle,
        author,
        setAuthor,
        isbn,
        setIsbn
    ];
}

export default AddBookComponent;

Add Book Button

Now we need a button that will actually add the book to the booklist. We create a handler function that will be called when the button is clicked.

import React, {useState} from 'react';

const AddBookComponent = ({setBookList, bookList}) => {

    const [
        title,
        setTitle,
        author,
        setAuthor,
        isbn,
        setIsbn
    ] = AddBookInputHook();

    const handleAddBook = () => {
        const newBookList = [...bookList];
        newBookList.push({
            title: title,
            author: author,
            isbn: isbn
        });
        setBookList(newBookList);
    }

    return (
        <div className={"page-container"}>
            <div className={"add-book-input-container"}>

                <div className={"book-container"}>
                    <div>
                        Title:
                    </div>
                    <input onChange={event => {
                        setTitle(event.target.value);
                    }}
                           value={title}
                    />
                </div>

                <div className={"book-container"}>
                    <div>
                        Author:
                    </div>
                    <input onChange={event => {
                        setAuthor(event.target.value);
                    }}
                           value={author}
                    />
                </div>

                <div className={"book-container"}>
                    <div>
                        ISBN:
                    </div>
                    <input onChange={event => {
                        setIsbn(event.target.value);
                    }}
                           value={isbn}
                    />
                </div>
                // We provide a reference to the function, not a function call such as handleAddBook()
                <button onClick={handleAddBook}>
                    Add Book
                </button>
            </div>
        </div>
    );
}

const AddBookInputHook = () => {

    const [title, setTitle] = useState('');
    const [author, setAuthor] = useState('');
    const [isbn, setIsbn] = useState('');

    return [
        title,
        setTitle,
        author,
        setAuthor,
        isbn,
        setIsbn
    ];
}

export default AddBookComponent;

Now we will import the AddBookComponent into the BookInventoryPage


import React, { useState } from 'react';
import BookListComponent from "./book-list-component";
import AddBookComponent from "./add-book-component";

const BookInventoryPage = ({}) => {

    const [bookList, setBookList] = useState([]);

    return(
        <div className={"page-container"}>
            <h1>
                My Book Library
            </h1>
            <BookListComponent bookList={bookList}/>
            <AddBookComponent bookList={bookList} setBookList={setBookList}/>
        </div>
    );
}

export default BookInventoryPage;


We need to add a little styling now, in 'index.css' add


.page-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

screen shot of app so far

All that is left is to add a few books and try it out.

screen shot of app

Conclusion

Our example app sure could use some styling, but it suits our purpose. In this article, we covered how we can use hooks, to have clean and focused state management. We can use hooks to control the lifecycle of functional components, and also control what components have access to state.

We can use custom hooks, to combine multiple hooks into one clean array. We can then decompose the props we need from the array.

Comments


user-429302

02/07/2021 14:58



Navagation