React Hooks - Simplified!!

ยท

11 min read

React Hooks -  Simplified!!

Get ready for a thrilling ride as we dive into the world of React Hooks, the supercharged tools every React developer adores! ๐Ÿ’ช๐Ÿฝ๐Ÿš€ These powerful hooks are essential for crafting fantastic web applications, and we'll explore some of the most important and useful ones. ๐ŸŒŸ But hold on! Before we embark on this epic journey, let's first get a sneak peek into what these magical hooks are all about in the realm of React. ๐ŸŽข Get ready to level up your React game with these incredible hooks! ๐ŸŒˆ๐Ÿ˜Ž

React Hooks

Let's understand it with an example...

" Imagine you want to build a house (your web application) using Lego blocks (React components). React is like the magic box that helps you organize and build your house quickly. Now, inside this magic box, there are special tools called React Hooks. These are little helpers that make your work even easier. They allow you to do some cool stuff without having to write a lot of complicated code. In the past, you could only build your house by using class-based components, which were a bit more difficult to understand. But with React Hooks, it's like having a special set of Lego pieces that you can use more simply and flexibly. "

React hooks are a set of powerful functions provided by the React library that help developers to manage states, work with DOM elements, and improve the performance of their code. They are useful tools that make our work with React easier and more efficient.

One of the primary advantages of using React hooks is that they allow us to handle stateful logic in functional components, eliminating the need for class components in many cases. This simplifies the code structure and makes it easier to understand and maintain. Also, react hooks have this very discrete identification mark that their name starts with "use" for instance..."useState()".

So by the time I assume we know what react hooks are now we can move forward to looking at some of them with some actual use cases:

  1. useState()

With useState() hook we can create and manage state variables within functional components, enabling dynamic changes in our application's UI based on user interactions or other events. It is one of the easiest and most important hooks of all. Now let's look into how it works...

import React from "react";
import { useState } from "react"; //1. import the hook

function UseState() {
    // 2. Declare useState with an initial value.
    //    useState hook returns 2 values here:
    //    (i) "num" will hold the value
    //    (ii) "setNum" will set the value of "num" 
    //    Note: the name of the second function shall always 
    //       start with "set," that's a convention.
    const [num, setNum] = useState(0);
    return (
        <div>
           {/* use the variable */}
            {num}
            <button
                onClick={() => {
                    // update the value of the variable
                    setNum(num + 1); 
                }}
            >
                Add
            </button>
        </div>
    );
}

export default UseState;
  1. useEffect()

    useEffect is a React hook that allows you to perform tasks after your component renders. It's useful for handling side effects like data fetching or subscriptions. You can also clean up resources using a return function inside useEffect.

    Here is a very real-world example showing its usage:

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

const MyComponent = () => {
  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  // This useEffect hook will run when the component is mounted
  useEffect(() => {
    // Simulate fetching data from an API (replace with your actual API call)
    fetch('https://api.example.com/data')
      .then((response) => response.json())
      .then((data) => {
        setData(data); // Update the state with the fetched data
        setIsLoading(false); // Set isLoading to false, as the data is now loaded
      });

    // This return function will be executed when the component is unmounted
    return () => {
      // Perform any necessary cleanup here (if needed)
      console.log('Component is unmounted, cleanup can be done here.');
    };
  }, []); // The empty dependency array means this effect runs only once when the component is mounted

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};

export default MyComponent;
  1. useRef()

useRef in React allows us to interact with DOM elements directly and store references to them. Unlike other hooks, useRef doesn't trigger re-renders. The "current" property of the returned object holds the element reference, making it ideal for tasks like focusing an input when a button is clicked or making changes to specific elements without causing unnecessary component re-renders.

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

// UseRef is used ot manipulate DOM elements...
// for instace to focus on a DOM element such as input when we click on a button
// or any other change in the element.

// UseRef never re-renders
// gives an object witha key "current", which stores the refernce of the that element.
// literally store that element!!
export default function RefTutorial() {
    const [num, setNum] = useState(0);

    const boxOneRef = useRef();
    const boxTwoRef = useRef();

    const HandleNumBox1 = () => {
        // this "boxOneRef" variable now references to the input element
        console.log(boxOneRef);
        boxOneRef.current.value = 1000;
        boxOneRef.current.focus();
        boxOneRef.current.style.width = "300px";
    };
    const HandleNumBox2 = () => {
        // this "boxTwoRef" variable now references to the input element
        console.log(boxTwoRef);
        boxTwoRef.current.value = 2000;
    };
    return (
        <div>
            <input
                ref={boxOneRef}
                type="number"
                value={num}
                onChange={(e) => {
                    setNum(e.target.value);
                }}
            />
            <input
                ref={boxTwoRef}
                type="number"
                value={num}
                onChange={(e) => {
                    setNum(e.target.value);
                }}
            />
            <button
                onClick={() => {
                    HandleNumBox1();
                }}
            >
                NUMBER1
            </button>
            <button
                onClick={() => {
                    HandleNumBox2();
                }}
            >
                NUMBER2
            </button>
        </div>
    );
}
  1. useLayoutEffect()

    useLayoutEffect() in React is similar to useEffect(), but it runs synchronously after every render, just before the browser updates the screen. This makes it ideal for tasks that require immediate DOM measurements or changes in response to state or prop updates. For example, if you need to position elements precisely on the screen based on their dimensions or calculate element sizes before the content is visually updated, useLayoutEffect() ensures you get accurate layout information.

    However, be cautious when using useLayoutEffect, as it can potentially cause performance issues if not used carefully. Since it runs synchronously, it can lead to longer render times and hinder a smooth user experience. In most cases, the regular useEffect() hook is sufficient for managing side effects, and you should consider useLayoutEffect() only when you specifically need to interact directly with the DOM in a synchronous manner.

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

export default function LayoutEffect() {
  const [visibility, setVisibility] = useState(false);
  const popup = useRef();
  const buttonRef = useRef();

  // useLayoutEffect is synchronous in nature
  useLayoutEffect(() => {
    // Check if the refs are defined before proceeding
    if (popup.current == null || buttonRef.current == null) return;

    // Get the bounding rectangle of the button
    const { bottom } = buttonRef.current.getBoundingClientRect();

    // Position the popup just below the button
    popup.current.style.top = `${bottom + 30}px`;

    // Logging for demonstration purposes
    console.log("Button top:", buttonRef.current.getBoundingClientRect().top);
    console.log("Button bottom:", bottom);
  }, [visibility]);

  return (
    <div>
      <button
        ref={buttonRef}
        onClick={() => {
          // Toggle the visibility state when the button is clicked
          setVisibility((visibility) => !visibility);
        }}
      >
        Click me!!
      </button>
      {/* Render the popup when visibility is true */}
      {visibility && (
        <h2 style={{ position: "absolute" }} ref={popup}>
          I am a popup
        </h2>
      )}
    </div>
  );
}
  1. useReducer()

    The useReducer hook in React is a powerful tool for managing complex state logic in functional components. It takes a reducer function and an initial state as arguments and returns the current state and a dispatch function to trigger state updates. The reducer function receives the current state and an action as parameters and returns a new state based on the action type. This allows us to centralize state updates and handle more advanced state management scenarios.

    We use useReducer when our component requires structured state management with multiple actions that modify the state. It simplifies handling interdependent state changes and avoids scattered state variables and update functions. This improves code organization and performance, particularly in components with deeply nested or frequently updated state structures.

import { useReducer } from "react";
import "./App.css"; 

// Reducer function to handle state changes
const reducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1, showText: state.showText };
    case "toggleShowText":
      return { count: state.count, showText: !state.showText };
    default:
      return state;
  }
};

function App() {
  // State management with useReducer
  const [state, dispatch] = useReducer(reducer, { count: 0, showText: true });

  return (
    <>
      <div>
        <h1>UseReducer</h1>
        <div className="card">
          <h1>{state.count}</h1>
          <button
            onClick={() => {
              // Dispatching actions to update state
              dispatch({ type: "INCREMENT" });
              dispatch({ type: "toggleShowText" });
            }}
          >
            Click Here!!
          </button>
          <p>{state.showText && <p>This is the text</p>}</p>
        </div>
      </div>
    </>
  );
}

export default App;
  1. useContext()

The useContext hook in React provides a straightforward and efficient way for components to access shared data throughout an application without the need for passing down props explicitly. It allows the creation of a global data container known as a context, where information can be stored and retrieved by components within its tree. To use useContext:

  1. create a context using the createContext function.

  2. Then, wrap the components that need access to the shared data with the Context.Provider, passing the data as a value prop.

  3. Finally, any component within the Context.Provider tree can use the useContext hook to consume the shared data and access its value. This powerful pattern simplifies communication between components and ensures a cleaner and more maintainable codebase, particularly when dealing with shared state or configuration settings.

import React, { createContext } from "react";
import ContextChildA from "./ContextChildA";

// useContext() hook
// createContext -> Provide value -> useContext

// creating context
const dataName = createContext();
const dataId = createContext();

function UseContext() {
    const username = "Manish";
    const userId = 1234;
    return (
        <>
            {/* providing context */}
            <dataName.Provider value={username}>
                <dataId.Provider value={userId}>
                    <ContextChildA></ContextChildA>
                </dataId.Provider>
            </dataName.Provider>
        </>
    );
}
export default UseContext;
export { dataName, dataId };
import React from "react";
import ContextChildB from "./ContextChildB";

export default function ContextChildA() {
// using the context
    return <ContextChildB></ContextChildB>;
}
import React, { useContext } from "react";
import { dataId, dataName } from "./UseContext";

export default function ContextChildB() {
    // using the context
    const name = useContext(dataName);
    const id = useContext(dataId);
    return (
        <div>
            <h1>
                {name}:{id}
            </h1>
        </div>
    );
}
  1. useMemo()

    • useMemo is a React hook that helps us optimize performance by caching the result of a function and reusing it whenever possible.

    • It prevents unnecessary re-execution of expensive calculations or complex functions, saving computational effort.

    • By providing a dependency array, we ensure that the function is recomputed only when the dependencies change, improving efficiency and responsiveness in our React components.

    • Note: It returns a memoized value!!

Here in this example:

The component renders the result of multiply as multiplication. Clicking the "Add" button increments add, and clicking the "Subtract" button decrements minus. The current values of add and minus are displayed on the screen. The console logs "****************" when the multiply function recalculates, demonstrating the optimization achieved by useMemo, as it avoids unnecessary calculations.

import React, { useMemo, useState } from "react";
// useMemo() :
export default function UseMemo() {
    // Define state variables "add" and "minus" using useState
    const [add, setAdd] = useState(0);
    const [minus, setMinus] = useState(100);

    // The following function "multiply" is commented out and replaced with useMemo
    // function multiply() {
    //     console.log("****************");
    //     return add * 10;
    // }

    // Define a memoized function "multiplication" using useMemo
    const multiplication = useMemo(
        function multiply() {
            console.log("****************");
            return add * 10;
        },
        [add] // Recalculate the result only when "add" changes
    );

    return (
        <div>
            <h1>UseMemo()</h1>
            {multiplication} {/* Display the result of the memoized "multiply" function */}
            <br />
            <button
                onClick={() => {
                    // Increment "add" state when the "Add" button is clicked
                    setAdd(add + 1);
                }}
            >
                Add
            </button>
            <span>{add}</span> {/* Display the current value of "add" */}
            <button
                onClick={() => {
                    // Decrement "minus" state when the "Subtract" button is clicked
                    setMinus(minus - 1);
                }}
            >
                Substract
            </button>
            <span>{minus}</span> {/* Display the current value of "minus" */}
        </div>
    );
}
  1. useCallback()

    The useCallback hook in React is used to optimize the performance of functional components that rely on callbacks, such as event handlers or functions passed down as props. When we use useCallback, it returns a memoized version of the provided callback function. This memoized function will only be recreated when any of its dependencies (specified in the dependency array) change. By doing so, unnecessary function recreations are avoided, leading to improved performance and reduced unnecessary renders.

    We use the useCallback hook in scenarios where:

    1. The component heavily relies on callbacks or functions that are passed down as props.

    2. The callbacks or functions are expensive to create.

    3. The callbacks depend on specific values or state changes.

import React, { useCallback, useState } from "react";
import CallbackChild from "./CallbackChild";

// useCallback(): returns a memoized function

export default function UseCallback() {
    // Define state variables "add" and "count" using useState
    const [add, setAdd] = useState(0);
    const [count, setCount] = useState(1);

    // Define a memoized function "Learning" using useCallback
    const Learning = useCallback(() => {
        console.log("i am a log message"); // Log a message when this function is called
    }, [count]); // Recreate the function only when "count" changes

    return (
        <div>
            <h1>UseCallback: </h1>
            <CallbackChild Learning={Learning} count={count}></CallbackChild>
            {/* Display the value of "add" */}
            <span>{add}</span>
            {/* Increment "add" state when the "Addition" button is clicked */}
            <button
                onClick={() => {
                    setAdd(add + 1);
                }}
            >
                Addition
            </button>
            {/* Display the value of "count" */}
            <span>{count}</span>
            {/* Increment "count" state when the "Count" button is clicked */}
            <button
                onClick={() => {
                    setCount(count + 2);
                }}
            >
                Count
            </button>
        </div>
    );
}
import { memo } from "react";
import React from "react";

function CallbackChild(props) {
    return (
        <div>
            this is a child of Callback!!
            {props.Learning()}
        </div>
    );
}

export default memo(CallbackChild);

Incredible! ๐ŸŽ‰ Did you feel the thrill of exploring the mesmerizing world of React Hooks? ๐Ÿ’ช๐Ÿฝโœจ These game-changing tools have unleashed a whole new era of web development, and we hope you've discovered the true magic they bring to your projects! ๐Ÿš€โœจ But, hey, the excitement doesn't stop here! If you loved this exhilarating journey, why not hit that follow button and stay tuned for more thrilling content? ๐ŸŒŸ๐Ÿ”ฅ We've got even more adventures lined up, and we can't wait to embark on them together! Happy coding and let's create wonders with React Hooks! ๐ŸŒˆ๐Ÿ˜Žโœจ

ย