5 Powerful React Hooks to Save You Time and Simplify Your Front-end Development

React hooks are a powerful feature that simplifies the development of React applications by enabling the reuse of logic across components. While many developers are familiar with the popular hooks like useState and useEffect, that are built-in React, there are virtually limitless possibilities to write custom hooks.

In this article, we'll explore 5 non-trivial React hooks that can save you time and simplify your front-end development. From loading your images in a lazy manner to tracking scroll progress and implementing real-time communication, these hooks will help you build more efficient and responsive applications. So, let's dive in and explore these powerful React hooks!

1. useScrollProgress#

The useScrollProgress hook allows you to track the user's progress as they scroll through a page. This can be useful for a variety of applications, such as tracking the progress of a user's reading or triggering animations when certain elements come into view.

One popular use case for the useScrollProgress hook is to implement a scroll progress bar at the top of the page. This progress bar displays the user's progress as they scroll through the page and can provide a visual indicator of how much content is left to read.

Here's the code for the useScrollProgress hook:

import { useState, useEffect } from 'react';

const useScrollProgress = (): number => {
  const [progress, setProgress] = useState<number>(0);

  useEffect(() => {
    const handleScroll = () => {
      const scrollY = window.scrollY;
      const height = document.documentElement.scrollHeight - window.innerHeight;
      const scrolled = scrollY / height;
      setProgress(scrolled);
    };

    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return progress;
};

export default useScrollProgress;

The useScrollProgress hook returns a number between 0 and 1 that represents the current scrolling progress of the page.

To use useScrollProgress in your React component, simply import the hook and call it:

import React from 'react';
import useScrollProgress from './useScrollProgress';

const TopProgressBar = () => {
  const progress = useScrollProgress();

  return (
    <div
      style={{
        position: 'fixed',
        top: 0,
        left: 0,
        width: `${progress * 100}%`,
        height: '3px',
        backgroundColor: 'blue',
        zIndex: 9999
      }}
    />
  );
};

export default TopProgressBar;

In this example, we create a TopProgressBar component that uses the useScrollProgress hook to get the current scrolling progress of the page. We then use this value to set the width of the progress bar div dynamically by setting the width property to ${progress * 100}% and apply some styles to make it look better.

And that's it! The TopProgressBar component will now render at the top of the page and show the current scrolling progress as a blue bar that expands as the user scrolls.

2. useIntersectionObserver#

The useIntersectionObserver hook is a React hook that allows you to track the intersection of a specified target element with its parent element or the viewport. This is useful when you want to trigger a certain action when an element enters or leaves the viewport or its parent container.

Suppose you have a long list of items that you want to render in your React component, but you only want to render the items that are currently visible in the viewport to improve performance. You can use the useIntersectionObserver hook to achieve this.

Here's the source code for the useIntersectionObserver hook:

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

type IntersectionObserverOptions = IntersectionObserverInit;

const useIntersectionObserver = <T extends Element>(
  ref: RefObject<T>,
  options?: IntersectionObserverOptions
): boolean => {
  const [isIntersecting, setIsIntersecting] = useState(false);

  useEffect(() => {
    const node = ref.current;

    const observer = new IntersectionObserver(([entry]) => {
      if (node && entry.intersectionRatio > 0) {
        setIsIntersecting(true);
        observer.unobserve(node);
      }
    }, options);

    if (node) {
      observer.observe(node);
    }

    return () => {
      if (node) {
        observer.unobserve(node);
      }
    };
  }, [ref, options]);

  return isIntersecting;
};

export default useIntersectionObserver;

And here's an example of how to use the hook in a component:

import { useRef } from 'react';
import useIntersectionObserver from './useIntersectionObserver';

const ExampleComponent = () => {
const ref = useRef<HTMLDivElement>(null);
const isIntersecting = useIntersectionObserver(ref, { threshold: 0.1 });

return (
  <div>
    <div style={{ height: "1000px", border: "1px solid red" }}>Scroll me</div>
    <div ref={ref} style={{ height: "1000px", border: "1px solid blue" }}>
      {isIntersecting ? "Visible content" : "Loading content..."}
    </div>
  </div>
);
};

export default ExampleComponent;

The useIntersectionObserver hook is a powerful and easy-to-use tool for monitoring the intersection between elements in a React application. It provides a simple way to detect when an element is entering or leaving the viewport, and it can be used to trigger dynamic updates in response to changes in the intersection state.

3. useInfiniteScroll#

The useInfiniteScroll hook can be an essential tool for building dynamic web applications that require infinite scrolling, such as social media feeds, e-commerce product listings or image galleries. With this hook, you can efficiently fetch data and render it as the user scrolls down the page.

Here's an implementation of the useInfiniteScroll hook:

import { useEffect, useState } from "react";

const useInfiniteScroll = (
  fetchMoreData: () => Promise<void>,
  threshold = 300
) => {
  const [isFetching, setIsFetching] = useState(false);

  useEffect(() => {
    const handleScroll = async () => {

      if (isFetching) return;

      const scrollPosition = window.innerHeight + window.scrollY;
      const scrollHeight = document.documentElement.scrollHeight;

      if (scrollPosition >= scrollHeight - threshold) {
        setIsFetching(true);
        await fetchMoreData();
        setIsFetching(false);
      }
    };
    window.addEventListener("scroll", handleScroll);

    return () => window.removeEventListener("scroll", handleScroll);

  }, [fetchMoreData, isFetching, threshold]);

  return isFetching;
};

export default useInfiniteScroll;

The hook takes two arguments: a fetchMoreData function to fetch additional data and an optional threshold value. The fetchMoreData function is called when the user reaches the bottom of the page, indicating that more data should be loaded. The threshold value is used to determine how far from the bottom of the page the user should be before the fetchMoreData function is called. By default, the threshold is set to 300px.

And here's how it can be used in a component:

import { useState } from "react";
import useInfiniteScroll from "./useInfiniteScroll";

const MyComponent = () => {
  const fakeFetchMoreData = async () => {
    await new Promise((resolve) => {
      setTimeout(resolve, 1000);
    });
  };

  const isFetching = useInfiniteScroll(fakeFetchMoreData, 100);

  return <div>{isFetching && <span>Loading...</span>}</div>;
};

The useInfiniteScroll hook can save a lot of time and effort when implementing infinite scrolling in React applications, especially when dealing with large datasets that need to be loaded progressively. By automating the process of fetching and displaying new data as the user scrolls down, the hook can eliminate the need for complex manual handling of scroll events and pagination logic.

4. useWebsocket#

Real-time communication between a client and server is an important aspect of modern web applications. The WebSocket protocol provides a reliable, bidirectional, and full-duplex communication channel that operates over a single socket connection.

The useWebsocket hook is a convenient way to use WebSockets in React applications. It abstracts away the low-level details of opening and managing a WebSocket connection, and provides a simple API for sending and receiving messages.

Here's an example implementation of the useWebsocket hook:

import { useState, useEffect } from "react";

const useWebsocket = (url: string) => {
  const [socket, setSocket] = useState<WebSocket | null>(null);
  const [message, setMessage] = useState<string | null>(null);

  useEffect(() => {
    const newSocket = new WebSocket(url);

    newSocket.onopen = () => {
      setSocket(newSocket);
    };

    newSocket.onmessage = (event) => {
      setMessage(event.data);
    };

    newSocket.onclose = () => {
      setSocket(null);
    };

    return () => {
      newSocket.close();
    };
  }, [url]);

  const sendMessage = (message: string) => {
    if (socket) {
      socket.send(message);
    }
  };

  return { sendMessage, message };
};

export default useWebsocket;

In this implementation, the hook takes a URL as a parameter, and returns an object with two properties: sendMessage and message.

The sendMessage function can be used to send a message over the WebSocket connection. The message property is updated with the most recent message received from the server.

Here's how you can use the hook inside of a component:

import { useEffect, useState } from "react";

import useWebsocket from "./useWebsocket";

const ChatRoom = () => {
  const [messages, setMessages] = useState<string[]>([]);
  const [latestMessage, setLatestMessage] = useState<string | null>(null);
  const { message, sendMessage } = useWebsocket(
    'ws://localhost:8080/chatroom'
  );

  useEffect(() => {
    if (message) {
      setLatestMessage(message);
    }
  }, [message]);

  useEffect(() => {
    if (latestMessage) {
      setMessages((messages) => [...messages, latestMessage]);

      setLatestMessage(null);
    }
  }, [latestMessage]);

  return (
    <div className="App">
      <h2>Click the button to see some magic happen!</h2>

      <button onClick={() => sendMessage(`Hello there: ${+new Date()}`)}>
        Send message
      </button>

      <ul>
        {messages.map((message) => (
          <li key={Math.random()}>{message}</li>
        ))}
      </ul>
    </div>
  );
};

export default ChatRoom;

The useWebsocket hook provides a simple and efficient way to handle WebSocket connections in React applications. It eliminates the need for manual setup and provides easy-to-use functions for sending and receiving messages.

5. useLazyImage#

In modern web development, it's common to have pages that contain a lot of images, which can negatively impact the performance of your web application. Fortunately, there's a way to avoid this issue by using the useLazyImage hook.

The useLazyImage hook enables you to delay the loading of images that are not yet visible on the screen. This is achieved by checking if the image is in the viewport using the IntersectionObserver API. The hook then returns a new JSX element for the image, with the src attribute set once the image is in the viewport.

Here's one of possible implementations of such a hook:

import React, { useState, useEffect, useRef } from "react";
import useIntersectionObserver from "./useIntersectionObserver";

type UseLazyImage = (src: string, alt: string) => () => JSX.Element;

const useLazyImage: UseLazyImage = (src, alt) => {
  const [imageSrc, setImageSrc] = useState<string>("");
  const imgRef = useRef<HTMLImageElement>(null);
  const isIntersecting = useIntersectionObserver(imgRef);

  useEffect(() => {
    if (isIntersecting) {
      setImageSrc(src);
    }
  }, [src, isIntersecting]);

  return () => <img src={imageSrc} alt={alt} ref={imgRef} />;
};

export default useLazyImage;

Noticed how we utilize the useIntersectionObserver hook previously created inside useLazyImage? This is the elegance of reactive hooks - their ability to be easily reused.

The hook is extremely easy to use, it might look something like this:

export default function App() {
  const Image = useLazyImage(
    "https://image.url",
    "Beautiful image"
  );

  return (
    <div className="App">
      <div style={{ height: "3000px", border: "1px solid red" }}>Scroll me</div>
      <Image />
    </div>
  );
}

Using the useLazyImage hook can greatly enhance your web application's performance by postponing the loading of images that are not currently visible on the screen. This hook can prevent the negative impact of loading a large number of images on your application's performance.

Conclusion#

In this article, we covered five custom React hooks that can significantly improve the performance and functionality of your web applications. The useScrollProgress hook allows you to track the scroll progress of a user, while the useIntersectionObserver hook enables you to easily detect when an element is in the viewport. The useInfiniteScroll hook simplifies the process of implementing infinite scrolling, while the useWebsocket hook allows you to easily integrate websockets into your React application. Lastly, the useLazyImage hook delays the loading of images until they are actually visible on the screen.

These hooks are not only useful, but also demonstrate the power of reactive programming in React. By encapsulating complex logic into reusable hooks, developers can save time and effort when building applications. Whether you need to track scroll progress, implement infinite scrolling, or use websockets, these custom hooks can make your life easier and your application more efficient.

I will see you soon in the next article!