DEV Community

Cover image for React Hooks Simplified
Piyush Jaiswal
Piyush Jaiswal

Posted on

React Hooks Simplified

If you have ever tried learning React, your brain might get stuck on React Hooks. But not anymore, I have explained important built-in react hooks in a very simplified manner with easy implementation. So let's dive deep into it…

In very simplified terms, Hooks are reusable logic or javascript functions similar to the reusable components we build in React.

Hooks are identified by their name which starts with “use”. For example, useState(use logic to maintain state), useEffect(use logic to perform some actions as an effect when something(s) changes), etc.

useState

In simple terms, useState is used to define variables whose values can be changed dynamically, and at the same time, those changes get reflected in your UI components.

Normal variables do change dynamically but their state doesn’t get updated in your UI because UI is not automatically aware of the fact that the state is changed.

Therefore, libraries such as react follow a unidirectional data flow, where changes to the data trigger updates to the UI. This means that you need to explicitly update the UI to reflect the changes in the variables.
Example: Simple Up and Down Counter
Explanation:
Whenever you click on the + or - button, you are trying to update the state of the variable dynamically with the help of the update function provided by useState hook. So whenever the state changes, the hook explicitly forces the updation of the component by re-rendering.

function UseStateHook() {
    const [data, setData] = useState({
        count: 0,
        id: 'X'
    });
Enter fullscreen mode Exit fullscreen mode
    const updateCount = (operator) => {
        operator === '+'
            ?
            setData((prev) => {
                return {
                    ...prev,
                    count: prev.count + 1
                }
            })
            :
            setData((prev) => {
                return {
                    ...prev,
                    count: prev.count - 1
                }
            });

    }

    return (
        <div className="App">
            <h2>Use State Hook</h2>
            <br />
            <button onClick={() => { updateCount('-') }}>-</button>
            <span>{data.count}</span>
            <button onClick={() => { updateCount('+') }}>+</button>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

useEffect

Many times you want to perform some action, when some state or variable changes.
This can be achieved by useEffect
It triggers a function, whenever the state of dependencies passed by you changes.
3 cases:

  1. To run the function on every render

    ```
    useEffect(()=>{
    //perform some action on every render
    });
    ```
    
  2. To run the function only once

useEffect(()=>{
//perform some action only once
}, []);
//empty dependency array
Enter fullscreen mode Exit fullscreen mode
  1. To run the function only when the passed dependencies gets changed.
useEffect(()=>{
//perform some action only when dependency variables change
}, [dependencyVarOne, dependencyVarTwo]);
//non-empty dependency array
Enter fullscreen mode Exit fullscreen mode

Cleanup function: useEffect is generally used for side effect functions like subscriptions, data fetching, etc. So when the component gets unmounted it’s required to release or clean up the resources

useEffect(()=>{
//side effect logic

return ()=>{
//cleanup logic
}
}, [/* dependencies */ ] );
Enter fullscreen mode Exit fullscreen mode

Example: When we change width of browser, as an effect the current width of browser gets displayed

import { useState, useEffect } from 'react'

function UseEffectHook() {
    const [width, setWidth] = useState(window.innerWidth);

    useEffect(() => {
        window.addEventListener('resize', () => {
            setWidth(window.innerWidth);
        })

        //cleanup
        return () => {
            window.removeEventListener('resize', () => {
                setWidth(0);
            });
        }
    }, [])
Enter fullscreen mode Exit fullscreen mode
    return (
        <>
            <h2>useEffect Hook</h2>
            <br />
            <p>
                Window Width:
                <span>
                    {width}
                </span>
            </p>
        </>
    );
}
Enter fullscreen mode Exit fullscreen mode

useMemo

It is used to improve the performance of your React app.

Sometimes, you have to perform functions such as sorting data, fetching a large amount of data, or some other kind of expensive functions
In that case, you don’t want your app to perform that function again and again until it is really required to recalculate as it makes your app slow

Here, useMemo hooks come into the picture, it caches the result of that expensive function and calls the function again only when dependencies change. Otherwise, it returns the previous cached result.
Example: In the following example, on initial render, both memoized and un-memoized function gets called.
But when we click on the update button it should only count up the value by one but it is also calling unmemoized function as seen on the console
But memoized function gets called only when the input location or vacancy gets updated

const hotelData = useMemo(() => {
        console.log("Memoized Function Call");
        //calling an API to get data
        return `Hotel Data ${userInput.location}  and ${userInput.vaccancy}`;
    }, [userInput.location, userInput.vaccancy])
Enter fullscreen mode Exit fullscreen mode

Complete code for this example can be found here

useCallback

It is also a performance improvement hook
It caches the entire function between re-renders but, it doesn’t call the function it only returns it.
In some cases, your function call might get changed based on some variables otherwise function remains the same
So for that purpose, useCallback can be used
Example: in this example, we have used useEffect() hook to compare whether our function gets changed. As you click on the update button, the state changes and component gets re-render but the function inside useCallback does not change
However, if you change the dependency variable, the function inside the callback gets changed

let func = useCallback((orderdetails) => {
        //api call to post data or any other function for ${value}
        console.log(`Order Placed ${value}`);
        return value;
    }, [value])

Enter fullscreen mode Exit fullscreen mode

Complete code for this example can be found here

useRef

Suppose you want to keep a value or function for future reference and that value or function doesn’t affect the rendering of your component, then useRef() can be used to reference that value or function between re-renders.

And later or you can use that, maybe for comparing, etc
For example, we can make use of useRef() to compare functions as we have done in the example of callback

Example:, as you update the state using the update button nothing happens, but when you change the input value
Two values get logged
One is the previous value of a function and its result
And then you get a new function and its result

const initialFunc = useRef(func);

    useEffect(() => {
        if (initialFunc.current !== func) {
            console.log("Function gets changed");
            initialFunc.current();
            func();
        }
    },[func])
Enter fullscreen mode Exit fullscreen mode

Complete code for this example can be found here

forwardRef

Sometimes you may want that your parent component can modify DOM or perform some action in the child component to which the parent doesn’t have direct access.
In that case, you can make use of the useRef() hook to hold that state and forwardRef() to expose that part to your parent
Example manipulating a child component from the parent component
In this example, we are changing the type of input of an input tag from the parent element
Initially, when you type in the input tag it takes normal text input but when you click on the button it converts to password type

import { useRef, forwardRef } from 'react';

// This example is referred from Official React Docs
// Create child component here or import it
const Child = forwardRef((props, ref) => {
    return <input placeholder={props.placeholder} ref={ref} />;
});

function ForwardRef() {
    const mainRef = useRef(null);

    function handleManipulation() {
        mainRef.current.setAttribute('type', 'password');
    }

    return (
        <div>
            <h2>Forward Ref: Manipulating Child Component From Parent</h2>
            <br />
            <Child placeholder="Enter Any Value..." ref={mainRef} />
            <br />
            {/* BUTTON OF PARENT TO MANIPULATE CHILD */}
            <button onClick={handleManipulation}>Convert Input type to Password</button>
        </div>
    );
}

export default ForwardRef;
Enter fullscreen mode Exit fullscreen mode

useReducer

This hook is used for state management. It allows you to define the initial state, define a function that will take the current state, and action performed as an argument, and contains the logic to update the state. It also has a dispatch function to help you update your state.

DISPATCH FUNCTION It is used to update the state to a different value and triggers the re-render and specify the action that occurred. Dispatch function updates the state only for the next render, and you’ll get the old value before the re-render completes.

REDUCER FUNCTION This function takes state and action as an argument. And the logic to update the state based upon action is present inside it. Action argument contains the action performed as defined by the dispatch function and state contains the current state.

Example: Let’s say a user logged in to our web app. He is not a subscriber hence the initial state is user type general but when he successfully subscribes, the state gets updated to the subscribed user, and accordingly UI changes.

import { useReducer } from "react";

function reducer(state, action) {
    if (action.type === "Subscribed") {
        return {usertype: "subscribed"}
    }
    else if (action.type === "Unsubscribed") {
        return {usertype: "unsubscribed"}
    }
    else if (action.type === "New") {
        return {usertype: "general"}
    }
    throw Error('Unknown Action');
}
Enter fullscreen mode Exit fullscreen mode
function UseReducerHook() {
    const [state, dispatch] = useReducer(reducer, { usertype: "general" });

    function handleClick(e) {
        switch (e.target.id) {
            case "Subscribe":
                dispatch({type: "Subscribed"})
                break;
            case "Unsubscribe":
                dispatch({type: "Unsubscribed"})
                break;
            default:
                dispatch({type: "New"})
                break;
        }
    }
return(
<div>
//code
</div>
);
Enter fullscreen mode Exit fullscreen mode

Complete code can be found here

Context API

It is another way of state management. It can be better understood by following explanation.

Example: It’s implementation can be understood by creating a dark and light mode functionality

import { createContext, useState} from "react";
import Heading from "../component/heading";

const Theme = createContext(null);

function ContextAPI() {
    const [state, setState] = useState({
        theme: "light",
        text: "Welcome to light"
    })

    function handleTheme(e) {
        e.target.id === "Light"
            ?
            setState({
                "theme": "light",
                "text": "Welcome to light"
            })
            :
            setState({
                "theme": "dark",
                "text": "Welcome to darkness"
            })

    }

    return (
        <Theme.Provider value={{
            theme: state.theme,
            text: state.text
        }} >
            <div className={state.theme === 'dark' ? "dark" : "light"}>
            <Heading />
            <button id="Light" onClick={handleTheme}>Light Mode</button>
            <button id="Dark" onClick={handleTheme}>Dark Mode</button>
            </div>
        </Theme.Provider>
    );
}

export {ContextAPI, Theme};
Enter fullscreen mode Exit fullscreen mode

Code of child component Heading is :

import { useContext } from "react";
import { Theme } from "../hooks/ContextAPI";

function Heading() {
    const contentTheme = useContext(Theme);
    return <h1 className={contentTheme.theme === "dark" ? "dark" : "light"}>{contentTheme.text}</h1>
}

export default Heading;
Enter fullscreen mode Exit fullscreen mode

So that's all from my side. I hope this article helped you to clear your understanding a bit. You can found the entire code over here
I'll try to come up with more articles with improved explanation and examples.

Top comments (4)

Collapse
 
bello profile image
Bello Osagie

Lovely article. Hope to see more. Context API is still my favorite for small and medium projects to manage state.

Collapse
 
piyushjaiswal1610 profile image
Piyush Jaiswal

Thanks Bello. Will come up with more articles asap

Collapse
 
hendrikras profile image
Hendrik Ras

It is not clear to me how changing the dependency variable changes the method in the useCallback example.

Collapse
 
piyushjaiswal1610 profile image
Piyush Jaiswal

Hi Hendrik, thanks for asking.
See I have commented a line over there which says a particular API call. It's to demonstrate that for differet types of orders or products, we'll have different API calls based upon design of our backend.
So what useCallback does is it initializes a function again only when dependency changes , that is, our function to call API changes for that particular use otherwise we don't need to re-declare or re-initialize that function on every re-render as a state changes.
Remember, useCallback hook is used for improvement in performance of our react app.
And for further understanding, I would advise you to go over the git repo github.com/piyush-jaiswal-projects... and then run the project, and you will get better understanding from the console logs.

I hope it clears your doubt. Please feel free to ask further queries, if any.