ReactJS - Custom Hooks



Hooks are integral part of a function component. They can used to enhance the feature of the function component. React provides few build-in hooks. Even though build-in hooks are powerful and can do any functionality, specialized hooks are necessary and in high demand.

React understands this need of the developer and allows to create new custom hooks through existing hooks. Developer can extract a special functionality from the function component and can create it as a separate hook, which can be used in any function component.

Let us learn how to create custom hooks in this chapter.

Create a custom hook

Let use create a new react function component with infinite scroll feature and then extract the infinite functionality from the function component and create a custom hook. Once the custom hook is created, we will try to change the original function component to use our custom hook.

Implement Infinite scroll functionality

The basic functionality of the component is to show a dummy list of todo item simply by generating it. As the user scrolls, the component will generate a new set of dummy todo list and append it to the existing list.

First of all, create a new react application and start it using below command.

create-react-app myapp
cd myapp
npm start

Next, create a react component, TodoList under component folder (src/components/TodoList.js)

function TodoList() {
   return <div>Todo List</div>
}
export default TodoList

Next, update the root component, App.js to use the newly created TodoList component.

import TodoList from './components/TodoList';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoList />
      </div>
   );
}
export default App;

Next, create a count state variable to maintain the number of todo items to e generated.

const [count, setCount] = useState(100)

Here,

  • The initial number of items to be generated is 100

  • count is the state variable used to maintain the number to todo items

Next, create a data array to maintain the dummy generated todo items and preserve the reference using useMemo hook.

React.useMemo(() => {
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
}, [count]);

Here,

  • Used useMemo to restrict the generation of todo items on every render.

  • Used count as a dependency to regenerate the todo items when the count changes

Next, attach a handler to the scroll event and update the count of the todo item to generated when the user moved to the end of the page.

React.useEffect(() => {
   function handleScroll() {
      const isBottom =
         window.innerHeight + document.documentElement.scrollTop
         === document.documentElement.offsetHeight;
      if (isBottom) {
         setCount(count + 100)
      }
   }
   window.addEventListener("scroll", handleScroll);
   return () => {
      window.removeEventListener("scroll", handleScroll);
   };
}, [])

Here we have,

  • Used useEffect hook to make sure the DOM is ready to attach the event.

  • Attached an event handler, scrollHandler for scroll event.

  • Removed the event handler when the component is unmounted from the application

  • Found that the user hits the bottom of the page using DOM properties in the scroll event handler.

  • Once the user hit the bottom of the page, the count is incremented by 100

  • Once the count state variable is changed, the component gets rerendered and more todo item are loaded.

Finally, render the todo items as shown below −

<div>
   <p>List of Todo list</p>
   <ul>
      {data && data.map((item) =>
         <li key={item.id}>{item.todo}</li>
      )}
   </ul>
</div>

The complete source code of the component is as follows −

import React, { useState } from "react"
function TodoList() {
   const [count, setCount] = useState(100)
   let data = []
   
   React.useMemo(() => {
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
   }, [count]);
   
   React.useEffect(() => {
      function handleScroll() {
         const isBottom =
            window.innerHeight + document.documentElement.scrollTop
            === document.documentElement.offsetHeight;
         
         if (isBottom) {
            setCount(count + 100)
         }
      }
      window.addEventListener("scroll", handleScroll);
      return () => {
         window.removeEventListener("scroll", handleScroll);
      };
   }, [])
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {data && data.map((item) =>
               <li key={item.id}>{item.todo}</li>
            )}
         </ul>
      </div>
   )
}
export default TodoList

Next, Open the browser and run the application. The application will append new todo item once the user hits the end of the page and goes on infinitely as shown below −

Implement Infinite Scroll Functionality

Implementing useInfiniteScroll hook

Next, let us try to create new custom hook by extracting the logic from the existing component and then use it in a separate component. Create a new custom hook, useInfiniteScroll (src/Hooks/useInfiniteScroll.js) as shown below

import React from "react";
export default function useInfiniteScroll(loadDataFn) {
   const [bottom, setBottom] = React.useState(false);
   React.useEffect(() => {
      window.addEventListener("scroll", handleScroll);
      return () => {
         window.removeEventListener("scroll", handleScroll);
      };
   }, []);
   React.useEffect(() => {
      if (!bottom) return;
      loadDataFn();
   }, [bottom]);
   
   function handleScroll() {
      if (window.innerHeight + document.documentElement.scrollTop
         != document.documentElement.offsetHeight) {
         return;
      }
      setBottom(true)
   }
   return [bottom, setBottom];
}

Here we have,

  • Used use keyword to name the custom hook. This is the general practice to start the name of the custom hook with use keyword and it is also a hint for the react to know that the function is a custom hook

  • Used a state variable, bottom to know whether the user hits the bottom of the page.

  • Used useEffect to register the scroll event once the DOM is available

  • Used a generic function, loadDataFn to be supplied during the creation of hook in the component. This will enable to create custom logic for loading the data.

  • Used DOM properties in the scroll event handler to track the user scroll position. when the user hits the bottom of the page, the bottom state variable is changed.

  • Returned the current value of the bottom state variable (bottom) and the function to update the bottom state variable (setBottom)

Next, create a new component, TodoListUsingCustomHook to apply the newly created hook.

function TodoListUsingCustomHook() {
   return <div>Todo List</div>
}
export default TodoListUsingCustomHook

Next, update the root component, App.js to use the newly created TodoListUsingCustomHook component.

import TodoListUsingCustomHook from './components/TodoListUsingCustomHook';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoListUsingCustomHook />
      </div>
   );
}
export default App;

Next, create a state variable, data to manage the todo list items.

const [data, setData] = useState([])

Next, create a state variable, count to manage number of todo list items to be generated.

const [data, setData] = useState([])

Next, apply the infinite scroll custom hook.

const [bottom, setBottom] = useInfiniteScroll(loadMoreData)

Here, bottom and setBottom are exposed by the useInfiniteScroll hook.

Next, create the function to generate the todo list items, loadMoreData

function loadMoreData() {
   let data = []
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
   setData(data)
   setCount(count + 100)
   setBottom(false)
}

Here,

  • Generated the todo items based on the count state variable

  • Incremented the count state variable by 100

  • Set the generated todo list to the data state variable using setData function

  • Set the bottom state variable to false using setBottom function

Next, generate the initial todo items as shown below −

const loadData = () => {
   let data = []
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
   setData(data)
}
React.useEffect(() => {
   loadData(count)
}, [])

Here,

  • Initial todo items are generated based on the initial count state variable

  • Set the generated todo list to the data state variable using setData function

  • Used useEffect to make sure that the data is generated once the DOM is available

Finally, render the todo items as shown below −

<div>
   <p>List of Todo list</p>
   <ul>
      {data && data.map((item) =>
         <li key={item.id}>{item.todo}</li>
      )}
   </ul>
</div>

The complete source code of the component is as shown below −

import React, { useState } from "react"
import useInfiniteScroll from '../Hooks/useInfiniteScroll'

function TodoListUsingCustomHook() {
   const [data, setData] = useState([])
   const [count, setCount] = useState(100)
   const [bottom, setBottom] = useInfiniteScroll(loadMoreData)
   
   const loadData = () => {
      let data = []
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
      setData(data)
   }
  
  function loadMoreData() {
      let data = []
      
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
      setData(data)
      setCount(count + 100)
      setBottom(false)
   }
   React.useEffect(() => {
      loadData(count)
   }, [])
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {data && data.map((item) =>
               <li key={item.id}>{item.todo}</li>
            )}
         </ul>
      </div>
   )
}
export default TodoListUsingCustomHook

Finally, open the browser and check the output. The application will append new todo item once the user hits the end of the page and goes on infinitely as shown below −

Implementing UseInfiniteScroll Hook

The behavior is same as that of TodoList component. We have successfully extracted the logic from the component and used it to create our first custom hook. Now, the custom hook can be used in any application.

Summary

Custom hook is an existing feature to separate the reusable logic from a function component and allows to make it really reusable hook across different function component.

Advertisements