Hooked on React: Mastering the Art of Hooks


Have you ever been asked by a client to change a functional component into a class component where all the UI and business logic are the same but the only difference is that, that you need to add some state?  

If so, then most developers will sympathize. But now we don’t have to do that because, as of React 16.8, we can now manage states in functional components too! 

Here we will learn about React hooks, what they are, and how to use them in our projects. React hooks are very useful in terms of code optimization, reusability, code simplicity, and much more. 

Now let’s get started, shall we?

What Are React Hooks?

React hooks are the functions that are responsible for hooking into React JS’s state and lifecycle features through function components. In React version 16.8, hooks were introduced as an entirely new feature. 

By using React hooks, we can use the state features and other React features without writing the class components.

React provides built-in hooks like <code>useState</code>, useEffect, useReducer, useRef, use Callback, useContext, and useMemo… and you can also create your own custom hooks. Using hooks, you can create fully functional components while using all React features simultaneously. As opposed to classes, hooks make it easy to create any functionality you need.

Benefits of Using React Hooks

Now that you have a brief idea about what actually React hooks are, let us check the benefits of using hooks:

  • Reusable: The first and most important benefit of hooks is their reusability. The hooks feature lets you reuse logic between components without affecting the design or structure of these components.
  • Efficient: By using hooks, you can synthesize code more efficiently.
  • Simplicity: When components become larger and perform multiple functions, it becomes harder to comprehend over time. Hooks can instead separate a single component into smaller functions based on their connections so you can work with the pieces independently.
  • Improved testing and legibility: Using hooks makes it easier to work with and test your setup, and they make the code look cleaner and easier to read.

Basics of React Hooks 

As now we are going to learn about React hooks, we should also know about some basic rules which are required to know. Here are the top features for hooks that we need to understand:

Rules of React Hooks

In this section, we’ll discuss mainly two rules that are vital to understanding hooks:

  • The top-level rule: In a component, hooks can only be called at the top level. There is no way to call hooks inside loops, conditions, or nested functions. You should instead use hooks at the top of your React functions.
  • The React rule: Hooks can only be called inside React function components and we cannot use them inside normal javascript functions.

Most Common React Hooks

There are two hooks in React that are the most important: useState and useEffect. Let’s get brief information about them:

useState()

The React hook useState() is used to manage the state of the function component. To use useState() we need to import the hook in our project, and to do that we need to write the below line:

import { useState } from "react";

useEffect()

The React hook useEffect() is used to perform side effects like updating DOM elements, fetching data, or managing the timers of our component. useEffect accepts two parameters one is function and another is dependency. 

Now let’s see how we can import that into our own project:

import { useEffect} from "react";

Working With React Hooks

After learning many essential concepts and theories, let’s dive deep into hooks now. To gain a better understanding of hooks, let’s take a look at each hook one by one with their examples:

useState()

When we want you to use the state in our React component, we can use the useState() hook at that time. Previously, to use state, we were required to create the class component, but that’s no longer the case.

Let’s check one example and also compare the function component with the class component to better understand:

import React, { useState } from 'react';

export default function App() {
    const [count, setCount] = useState(0); 
    return (
        <div> 
            <p>You clicked {count} times</p> 
            <button onClick={() => setCount(count + 1)}> Click me </button> 
        </div> 
    );
}

Here, App() is a functional component of React. In that component, we have defined count as a state variable and setCount is a method that we can use to modify state value which can be seen on button click, and useState(0) is used to define the default value for state count.

Now let’s check how we use that in our class component:

class Example extends React.Component {
    constructor(props) {
        super(props); 
        this.state = { count: 0 }; 
    } 
    render() { 
        return ( 
            <div> 
                <p>You clicked {this.state.count} times</p> 
                <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button>
            </div> 
        ); 
    }
}

So here, we have added a class component and managed state in that. To define a state we need to create a constructor, and to manage or modify the state we need to call the setState method. Plus, to access the state data, this keyword is required, making useState a very efficient way to manage the state — much more so than the traditional state storage method.

useEffect()

useEffect() is the same as componentDidMount and componentDidUpdate methods, which we usually use in our class component.

import React, { useState, useEffect } from 'react'; 

export default function App() { 
    const [count, setCount] = useState(0);
    useEffect(() => { 
        document.title = `You clicked ${count} times`; 
    }); 
    return ( 
        <div> 
            <p>You clicked {count} times</p> 
            <button onClick={() => setCount(count + 1)}> Click me </button> 
        </div> 
    ); 
}

We have modified our example based on the previous example that we use in the useState point. In this sample moving one step forward when the state value changes we and the component load again at a time we are also modifying the title of our document.

Let us also check the example for the class component for the same use case:

class App extends React.Component { 
    constructor(props) { 
        super(props); 
        this.state = { 
            count: 0 
        }; 
    } 
    componentDidMount() { 
        document.title = `You clicked ${this.state.count} times`; 
    } 
    componentDidUpdate() { 
        document.title = `You clicked ${this.state.count} times`; 
    } 
    render() { 
        return ( 
            <div> 
                <p>You clicked {this.state.count} times</p> 
                <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> 
            </div> 
        ); 
    } 
}

Here, we need to create two different methods to manage the title of our document, which we did with just one single method — useEffect — in our function component.

useContext()

Using React Context, we can manage the state globally. It facilitates the sharing of states between deeply nested components that cannot be achieved with useState alone.

Let’s check the example with component to understand how the useContext hook works:

import { useState } from "react"; 
export default function Example1() { 
    const [username, setUsername] = useState("David"); 
    return ( 
        <> 
            <h1>{`Hey there ${username}!`}</h1> 
            <Example2 username={username} /> </> 
    ); 
} 

function Example2({ username }) { 
    return ( 
        <> 
            <h1>Example 2 Component Load</h1> 
            <Example3 username={username} /> </> 
    ); 
}
 
function Example3({ username }) {
    return ( 
        <> 
            <h1>Example 3 Component Load</h1>
            <Example4 username={username} /> </> 
    ); 
}

function Example4({ username }) { 
    return ( 
        <> 
            <h1>Example 4 Component Load</h1> 
            <Example5 username={username} /> </> 
    );
} 

function Example5({ username }) { 
    return ( 
        <> 
            <h1>Example 5 Component Load</h1> 
            <h2>{`Hey there ${username} fianal call!`}</h2> </> 
    ); 
}

Here is the link to the sandbox where you can see the working code of the above snippet.

So as we can see in the above code, we have passed the username state to each component from component 1 which is Example1 to Example5, and we weren’t required to use the state in Example2, Example3, or Example4 to reach to Example5. When working on a very complex project, using this feature makes it easy to transfer data from one component to another.

useReducer()

In most ways, useReducer() is similar to useState(), except that useReducer allows custom logic for state values to be added.

The syntax for useReducer() is:

useReducer(<reducer>, <initialState>)

Here, <reducer> is a function that contains our custom logic for the state value, and initialState is a simple value or an object.

This hook returns the current state as a dispatch method.

Let’s take a look at an example of the hook useReducer() now:

import { useReducer } from "react"; 

const initialTodos = [ 
    { 
        id: 1, 
        title: "Task 1", 
        complete: false 
    },
    { 
        id: 2, 
        title: "Task 2", 
        complete: false 
    }, 
    { 
        id: 3, 
        title: "Task 3", 
        complete: false 
    } 
]; 
const reducer = (state, action) => { 
    switch (action.type) { 
    case "COMPLETE":
        return state.map((todo) => { 
            if (todo.id === action.id) { 
                return { ...todo, complete: !todo.complete }; 
            } else { 
                return todo; 
            } 
        }); 
    default: 
        return state; 
    } 
}; 

export default function Todos() { 
    const [todos, dispatch] = useReducer(reducer, initialTodos); 
    const handleComplete = (todo) => { 
        dispatch({ type: "COMPLETE", id: todo.id }); 
    }; 

    return ( 
        <> 
            <h1>Current tasks:</h1> 
            {todos.map((todo) => ( 
                <div key={todo.id}> 
                    <label> 
                        <input type="checkbox" checked={todo.complete} onChange={() => handleComplete(todo)} /> {todo.title} 
                    </label> 
                </div> 
            ))} 
            <h1>Completed tasks:</h1> 
            {todos.map((todo) => { 
                if (todo.complete) 
                    return ( 
                        <div key={todo.id}> 
                            <label>{todo.title}</label> 
                        </div> 
                    ); 
                }
            )} 
        </> 
    ); 
}

Here is the link to the sandbox where you can see the working code of the above snippet.

In the above example, we have created a simple to-do application that has a list of tasks, and when we mark them complete it will show us those tasks in the completed tasks list.

useLayoutEffect()

React hook useLayoutEffect() is almost identical to useEffect the only difference is that useLayoutEffect is used for DOM mutation. useLayoutEffect will run synchronously after React has done with all the DOM mutations. It is also useful for DOM measurements for example scroll position.

Now let’s check out the example of useLayoutEffect:

import React, { useLayoutEffect, useState } from "react";

export default function App() { 
    const [value, setValue] = useState("Hi"); 
    useLayoutEffect(() => { 
        if (value === "Hi") { 
            // Changing the state 
            setValue("Hello useLayoutEffect");
        } 
        console.log("UseLayoutEffect is called with the value of ", value); 
    }, [value]); 

    return <div>{value}!</div>; 
}

Here is the link to the sandbox where you can see the working code of the above snippet.

So in the above example, we use the useLayoutEffect hook. As per the snippet when the document load it will run the hook and print ‘Hello useLayoutEffect!’ as the output.

useCallback()

A React hook useCallback() returns a memoized version of the callback, which will only change if any of its dependencies change as well. This is useful when a child component is dependent on the parent component to change its value.

Now let’s check out the example of useCallback:

import React, { useState, useCallback } from "react"; 

export default function App() { 
    const [number, setNumber] = useState(1); 
    const [newNumber, setArray] = useState([]);
    const incCount = () => { 
        setNumber((prev) => prev + 1);
    }; 
    const addNewNumber = useCallback(() => { 
        setArray([...newNumber, Math.floor(Math.random() * 100)]); 
    }, [newNumber]); 

    return ( 
        <div> 
            <h2>Parent component</h2> 
            <span> Counter value is: {number}</span> 
            <br /> 
            <button onClick={incCount}> Click to increate the counter! </button>
            <Childdata newNumber={newNumber} addNewNumber={addNewNumber} /> 
        </div> 
    ); 
} 

function Childdata({ newNumber, addNewNumber }) { 
    return ( 
        <> 
            <h2>Child component</h2> 
            <span>Array values are: </span> 
            {newNumber.map((number, index) => { 
                return <span key={index}>{number},</span>; 
            })} 
            <br /> 
            <button onClick={addNewNumber}>Add new number to the Array!</button> 
        </> 
    ); 
}

Here is the link to the sandbox where you can see the working code of the above snippet.

In the above example when the newNumber value changes, the Childdata component will also render as it is dependent on its parent component which is the App component, and we are also passing the useCallback to the child component.

useMemo()

React hook useMeno returns memoized value. useCallback and useMemo are almost similar, the only difference is that useMemo returns memoized value whereas useCallback returns memoized function, also both are used to improve the performance and memory usage of the React application.

Now let’s check the example of the useMemo:

import React, { useState, useMemo } from "react"; 

export default function App() { 
    const [number, setNumber] = useState(0); 
    const squaredNum = useMemo(() => { 
        return squareNum(number); 
    }, [number]); 

    const [counter, setCounter] = useState(0); 
    const onChangeHandler = (e) => { 
        setNumber(e.target.value); 
    }; 
    const counterHander = () => { 
        setCounter(counter + 1); 
    }; 

    return ( 
        <div className="App"> 
            <input type="number" placeholder="Enter a number" value={number} onChange={onChangeHandler} ></input> 
            <div>Square of the above number: {squaredNum}</div> 
            <br /> 
            <div>Counter : {counter}</div> 
            <button onClick={counterHander}>Increase Counter</button> 
        </div> 
    ); 
} 

function squareNum(number) { 
    return Math.pow(number, 2); 
}

Here is the link to the sandbox where you can see the working code of the above snippet.

In the example above, where squareNum is passed inside useMemo, and inside array dependencies, increasing the counter does not cause squareNum to repeat if the input field number remains the same.

useRef()

React hook UseRef returns mutable ref objects, allowing us to persist values between renders within the React framework. 

Now let’s check the example of useRef:

import { useState, useEffect, useRef } from "react"; 

export default function App() { 
    const [val, setVal] = useState(0); 
    const sum = useRef(0);
 
    useEffect(() => { 
        sum.current = parseInt(sum.current) + parseInt(val); 
    }); 
    return ( 
        <> 
            <input value={val} onChange={(e) => setVal(e.target.value)} /> 
            <h1>Summation: {sum.current}</h1> 
        </> 
    ); 
}

Here is the link to the sandbox where you can see the working code of the above snippet.

In the above example, whichever value you enter the value in the input box it will do a summation of those values.

Creating Custom React Hooks

We have seen earlier in this blog that hooks provide us with a fantastic feature, which is that they can be reused. Therefore when we have a logic that we are going to use in multiple components, we can create a custom hook for that logic and use that hook in all the other components. 

As per the official React website, the definition of a custom hook is “a JavaScript function whose name starts with ‘use’ and that may call other hooks.” Note that the hook must start with the use keyword — for example, useInsertData, useGetData, useStatus, etc.

In contrast to a React component, a custom Hook does not require a specific signature. Also, there is nothing predetermined as to what arguments it will accept, or what it should return if anything. The function is the same as any other and lies empty until you define its role(s).

Also, if you want to check out more details about custom hook now, kindly check this page of the official React site.

Testing React Hooks

When it comes to React, a component with hooks is just like any other component. In the case that your testing solution doesn’t have any dependency on React internals, the process of testing components with hooks shouldn’t be different from how you normally test them.

To test use cases first we need to define the test cases, after that, we can decide if we want to use any third-party library or anything available open source or we can also go without using any library.

Let’s check a simple example using the fetch method for a test case:

function fetchTest(url, suffix = "") { 
    return new Promise((resolve) => 
        setTimeout(() => { 
            resolve({ 
                json: () => Promise.resolve({
                    data: url + suffix, 
                }),
            });
        }, 200 + Math.random() * 300) 
    ); 
}

By default, this modified fetch returns the parameter URL as the data value, if the response type is JSON. Also, in addition, it adds a random delay of 200 ms to 500 ms to the response. The response can be changed by simply changing the second argument suffix. 

It may come as a surprise to you at this point, but why does it take so long? We should simply return the response instantly — wouldn’t that be better? 

In order to achieve as much real-world as possible, it’s important to try to replicate as much as possible the world itself. By returning the hook instantly, you won’t be able to test it correctly because the real world doesn’t operate instantaneously. You need instead to mimic the average time(s) it might take for these processes to function on your users’ ends.

If you are interested in learning more about testing, please review the Testing Recipies documentation on React’s official website.

Summary

You should now have a much deeper understanding of React hooks, how they are used, the advantages of using them, and even how to create custom hooks that can be used in multiple components.

As you know hooks are reusable and very simple to use also they are preferable over lifecycle methods in class components. So you should always try to use hooks and function components in your project to improve performance and optimization.

Functional components are given a great deal of power by hooks. Hopefully, this blog will be useful in your everyday use of React hooks.

One thought on “Hooked on React: Mastering the Art of Hooks

Add yours

  1. Thanks for sharing the article, We have read the article and much appreciated.

    We (Scalent) as Golang Specialized Development Services, top 10 Golang development Company in India also write the some blogs which are completely based on Golang. Hope you will like it.

    Liked by 1 person

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Create a website or blog at WordPress.com

Up ↑