React Hooks: Part 1

To-do.png

React is a Javascript library for building user interfaces. You may have worked with class components, and didn't understand why developers are using hooks. As a beginner, it's hard to know which one to use, for example, there are legacy codes written in class components, and you need to refactor it to functional components using Hooks, or you just want to learn the new React, and don't want to invest time learning class components. If you are thinking like this, you are in the right place. This tutorial will show you the details about using useState and useEffect hooks, as you progress you can go on with different hooks, and libraries. React is a huge library and you will be shocked that there are so many different things that you can do with React only. So, fasten your seatbelts, and enjoy the ride.

What we will cover

Throughout this tutorial, we’ll learn how to set state using the useState and useEffect Hooks. We’ll create different components and for the last component, we will combine these two hooks and create a recipe app that will fetch new recipes from an API. More specifically, we will learn how to:

  • use useState with an array/object for the default value
  • use useEffect without a Dependency Array, with an Empty Dependency Array, with a Non-empty Dependency Array, and with a cleanup function
  • fetch an API with useEffect

By the end of the tutorial, you will have the following skill sets:

  • The hands-on practical and real-life scenario of basic React Application using React Hooks.
  • You will manage state in a functional component using Hooks, and you’ll have a foundation for more advanced Hooks such as useCallback, useMemo, and useContext.

Here is the live demo of the end result.

View Demo

Prerequisites

  • Basic familiarity with HTML & CSS.
  • Basic knowledge of JavaScript ES6.
  • Basic understanding of the DOM.
  • Basic React knowledge like props, components, one way-data-flow

What Are React Hooks?

Hooks are a new addition in React 16.8. With the help of hooks, we can use state and other React features without writing a class. Hooks allow for attaching reusable logic to an existing component and use state and lifecycle methods inside a React functional component. We can organize the logic inside a component into reusable isolated units, and we have a better separation of concerns. React Hooks makes developing apps easier with less complexity. It improves the readability and organization of components. We can create custom hooks to reuse code across our app.

I want to start our tutorial with a general overview of our hooks. This will you a big picture of hooks, then we will dig deeper into our two commonly used hooks. You can just skim over these and use them as a reference when you need them. This may be overwhelming, but no need to worry about it right now. diagram.png

  • useState is the most common hook that you will see. It is the state hook for declaring the state in our components.
  • useEffect is used for side effects like fetching data from an API.
  • useRef is used to allow access directly to an element in the DOM and to create a mutable ref object that won't trigger a rerender.
  • useContext allows us to easily work with the React Context API (solving the prop drilling issue).
  • useReducer is an advanced version of useState for managing complex state logic. It’s quite similar to Redux.
  • useMemo returns a value from a memoized function.
  • useCallback returns a function that returns a cacheable value. Useful for performance optimization if you want to prevent unnecessary re-renders when the input hasn’t changed.
  • useLayoutEffect similar to useEffect, they differ in when they trigger.
  • useImperativeHandle to customize the instance value that’s exposed to parent components when using ref.
  • useDebugValue displays a label for custom Hooks in React Developer Tools.

In this tutorial, we will focus on the most common hooks: useState and useEffect. But first, let's start with why we need hooks in the first place.

Why Hooks?

Before Hooks:

  • We would need to understand how this keyword works in Javascript and to remember to bind event handlers in class components.
  • A common way to attach logic externally to a component was to use the render props or Higher-Order Components pattern.

We needed to share stateful logic in a better way. React is designed to render components, and it doesn't know anything about routing, fetching data, or the architecture of our project. There wasn't a particular way to reuse stateful component logic and this made the code harder to follow. So, React Hooks came to the rescue. superman.gif Hooks are just functions that are exported from the official React page. They allow us to manipulate components in a different manner.

There are some rules about how to use hooks. The following rules are:

  1. Only call hooks at the top level of the component.
  2. Don't call hooks inside loops, conditionals, or nested functions.
  3. Only call hooks from React functional components.
  4. Call them from within React functional components and not just any regular Javascript function.
  5. Hooks can call other Hooks.

You may ask, Should I need to change my class components to hooks? Actually NO, we can still use class components as 16.8 is backward compatible.

Application Tools

  • Install NodeJS and make sure it is the LTS(long term support) version. LTS version is a less stable version of NodeJS. We will use NPM (node package manager) and we will use it to install create-react-app.

node.png

  • Install your preferred code editor or IDE. I will be using Visual Studio Code. You can download it from this website. It is free to use.

vscode.png

  • create-react-app is an npm package that we can bootstrap our React application without any configuration.

cra.png

How to Install React Hooks?

You need to either upgrade the version of React and React-DOM to 16.8.2 or create a new React project using Create React App.

In this tutorial, we’ll use Create React App to create a new React project.

Open up your terminal and run the following to create the new project:

# cd into the directory you want to create the project.
cd desktop

# type this command to install create-react-app, you can give any name for the app.
npx create-react-app myApp

# Let's go inside our project folder, type the name of our project, and `cd` into it.
cd myApp

# open the project files with Visual Studio or any code editor
# start the app
npm start

Your default browser will open and you’ll see your new React app.

app.png

Now we can see our app is up and running. Before starting our app, let's make some cleanup and remove some of the files that we will not use.

Let's remove App.test.js, index.css, logo.svg, setupTests.js from the src folder. You can copy and paste the basic structure for App.js and index.js from the code snippets below.

// src/App.js

import React from 'react';
import './App.css';

function App() {
  return <div></div>;
}

export default App;
// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Also, we can remove logo files from the public folder, now my files are looking like this:

fs.png

Throughout this tutorial, we will create multiple components and you need to import the components to App.js to see how it is working. I have used react-router-dom to show all the components in one app, but we will not talk about routing in this tutorial. That's why you need to create a folder under src directory named components and create the components there, then import it to App.js. Example:

// src/App.js

import React from 'react';
// import the new component here
import StateHook from './components/StateHook';

import './App.css';

function App() {
    return (
      <div>
      {/* render the component  */}
        <StateHook />
    </div>
    );
}

export default App;

Styling the Application

I have used Semantic UI and custom CSS for styling. For Semantic UI, I have added a link tag inside my public > index.html file like this:

<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet" />

className attribute values are coming from Semantic UI or CSS. You don't need to focus on those.

For the CSS code, you can copy-paste these inside App.css file.

/* src/App.css */

body {
  padding: 10px;
  font-family: sans-serif;
  background-color: #f69e9e;
  line-height: 1.2;
}

.container {
  text-align: center;
  margin-top: 5rem;
  width: 90vw;
  margin: 0 auto;
  max-width: 1170px;
  min-height: 100vh;
}

h1 {
  color: #371e30;
  letter-spacing: 10px;
  text-transform: uppercase;
  margin: 0 0 10px;
}

h2 {
  font-weight: bold;
  font-size: 1em;
  line-height: 1.2em;
  padding: 0;
  color: #222;
  font-size: 30px;
}

a {
  text-decoration: none;
  color: #222;
  font-weight: 600;
}

ul {
  vertical-align: bottom;
  margin: 0 20px;
  padding: 0 0 25px 0;
  text-align: left;
}

p {
  font-weight: bolder;
  font-size: 1em;
  text-align: left;
}

input[type='text'] {
  width: 60%;
  padding: 12px 20px;
  margin: 8px 0;
  display: inline-block;
  border-radius: 4px;
  box-sizing: border-box;
  background: #fff;
}

.btn {
  display: block;
  margin: 0 auto;
  padding: 0.25rem 0.75rem;
  border-color: transparent;
  text-transform: capitalize;
  font-size: 1.4rem;
  margin-top: 2rem;
  cursor: pointer;
  background-color: #ddd;
  color: black;
}

.btn:hover,
a:hover {
  border: 1px solid #df57bc;
  background-color: #df57bc;
  padding: 5px;
  color: #fff;
}


.recipe {
  border-radius: 10px;
  margin: 40px;
  min-width: 40%;
  padding: 40px;
  max-width: 400px;
  background: white;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}

Now, with this, we are ready to go. 🥳

The useState Hook

State helps build highly performant web apps. To keep track of our application logic, we need to use useState. We can reflect any UI(user interface) changes via changes in state.

useState function lets us use state in a functional component.

In order to use useState in our component, we have to import useState first. useState is a named export; so, we will export it with curly braces.

import React, { useState } from 'react';

Let's make an example of how to use useState.

// src/components/StateHook.js
import React, {useState} from 'react';

const StateHook = () => {
  
  const [title, setTitle] = useState('hello world');

// update the state with setTitle function
  const handleClick = () => {
    setTitle('React is cool');
  };

  return (
    <div className="container">
      <h2>{title}</h2>
      <button type="button" onClick={handleClick} className="btn">
        Change title
      </button>
    </div>
  );
};

export default StateHook;

state.gif

useState returns an array of two items:

  • the first element is the current value of the state.
  • the second is a state setter/updater function, which we use to update our state.

In short, state tracks the value of our state. The setter function updates the state and rerenders JSX elements.

// destructuring an array
// initial state is 'hello world'
const [title, setTitle] = useState('hello world');

Developers generally prefer array destructuring with useState hook or we need to write more verbose code like this:

const items = useState('hello world');
const title = items[0];
const setTitle = items[1];

You may ask, How React knows when to render? React components will only rerender when their props or state have changed. Props are passed into a component and read-only, whereas a state holds information about the component, and can be updated. During the initial render, the returned state is the same as the value passed as the first argument (initialState).

So, here we updated our state with the setTitle setter function and passed a different string inside of it. When the button gets clicked, we are updating the state with the onClick event handler. The setTitle function accepts a new state value and rerenders the component.

In class components, a state is always an object, with the useState hook, the state does not have to be an object. Instead, you can break up state into multiple pieces that you can update independently.

useState with objects

What I mean by the title is we will create an object inside our useState hook, instead of passing a string. The initial value of useState can be of any type, like an array, an object, a boolean, a number, a string, etc.

// src/components/StateHookObject.js
import React, {useState} from 'react';

const StateHookObject = () => {
  // pass an object for the initial state
  const [name, setName] = useState({firstName: '', lastName: ''});

  return (
    <form>
        <input
          type="text"
          value={name.firstName}
          // set firstName to whatever is typed inside the input field
          onChange={(e) => setName({firstName: e.target.value})}
        />
        <input
          type="text"
          value={name.lastName}
          // set lastName to whatever is typed inside the input field
          onChange={(e) => setName({lastName: e.target.value})}
        />
        <h2>First name is: {name.firstName}</h2>
        <h2>Last name is: {name.lastName}</h2>
    </form>
  );
};

export default StateHookObject;

Now, let’s break down the code above to explain what we’ve added and how it works.

  • importing the useState hook from React
  • creating a new constant that returns name and setName from useState.
  • initializing the useState hook with an object.
  • create a form to display our inputs and h2 tags
  • add value property and onChange event handler to our inputs. e.target.value will give us the value inside the input field.

The important part about this component, we need to focus on the onChange event handler. onChange event fires whenever the user types in something. Whenever the first input value changes, we update the firstName property, and when the second input value changes, we update the lastName property.

Okay, everything looks perfect. Now, let's test our code. demo3.gif

We have a problem with updating our states; so, as you can see, we can update both input fields; but when we switch between them we cannot keep track of our old state.

Let's add this one line of code to see what is happening.

// src/components/StateHookObject.js

// ...
  <h2>Last name is: {name.lastName}</h2>

// add this line to your code
  <h2>{JSON.stringify(name)}</h2>
  </form>

demo4.gif When we type for the first name input, the last name input is disappearing. Because state doesn't automatically merge and update the state. useState does not "merge" its arguments with the old state. They just set the state. Every time, with every rerender we don't mutate our state, we get a completely new state, we can change our state with the setter function.

In class components setState will merge the state; useState hook will not merge the state. To handle this, we will use the spread operator to merge. With this, the setter object will copy everything inside the name object, and overwrite the firstName or lastName fields with a different value.

Let's see this in our code:

// src/components/StateHookObject.js

// ...
return (
  <form>
      <input
        type="text"
        value={name.firstName}
        // add the spread operator
        onChange={(e) => setName({...name, firstName: e.target.value})}
      />
      <input
        type="text"
        value={name.lastName}
        // add the spread operator
        onChange={(e) => setName({...name, lastName: e.target.value})}
      />
      <h2>First name is: {name.firstName}</h2>
      <h2>Last name is: {name.lastName}</h2>
      <h2>{JSON.stringify(name)}</h2>
  </form>
);

demo5.gif

  • We shouldn't mutate the state in our components.
  • We need to pass the previous state by the state setter(with the spread operator).

useState with arrays

todo.png

Now, we will make another component that we will use an array for the initial state. Let's see what will happen.

// src/components/StateHookArray.js

import React, { useState } from 'react';

const StateHookArray = () => {
    const [ items, setItems ] = useState([
      { id: 1, listItem: 'go on a holiday' },
      { id: 2, listItem: 'go hiking' },
      { id: 3, listItem: 'learn React Hooks' }
    ]);

    // remove items
    const removeItem = (id) => {
      setItems(items.filter((item) => item.id !== id));
    };

    const addItem = () => {
      setItems([
        // don't mutate the array, use spread operator to get the previous state
        ...items,
        // add new item
        {
          id: 4,
          listItem: 'meet deadlines'
        }
      ]);
    };

    return (
      <div className="ui container">
        <div className="ui grid">
          {items.map((item) => {
            const { id, listItem } = item;
            return (
              <div key={id} className="row">
                <h2 className="five wide column">{listItem}</h2>
                {/* when it is clicked, remove the individual item */}
                <button className="three wide column btn" onClick={() => removeItem(id)}>
                  remove
                </button>
              </div>
            );
          })}
        </div>
        {/* when it is clicked, empty the whole array */}
        <button className="btn" onClick={() => setItems([])}>
          Delete all
        </button>
        {/* when it is clicked, add one new item to the list */}
        <button className="btn" onClick={() => addItem()}>
          Add Item
        </button>
      </div>
    );
  };

export default StateHookArray;

Let’s break down the code above to explain what we’ve added and how it works.

  • importing the useState hook from React
  • creating a new constant that returns items and setItems from useState.
  • initializing the useState hook with an array of objects.
  • returning some JSX elements to display our array items and Semantic UI to add a grid system
  • mapping over the array to get each array item
  • adding a remove button for every item when it is clicked, we can remove the individual item
  • adding a button with an onClick handler that invokes the setItems function of useState with an empty array. So, we can remove everything from our array.
  • adding an add button, when it is clicked on it adds a new item. We merge the old state with the updated state with the help of ES6 spread operator. demo2.gif And yes, we are done with the useState hook. 🥳
blog

copyright© 2021Hulya Karakaya all rights reserved