Week 9.1 Custom Hooks
In this lecture, Harkirat presents a comprehensive guide to Custom Hooks in React
The discussion begins by contrasting class-based
and functional component
approaches. It then delves into the rationale behind the advent of custom hooks, highlighting their role in maintaining cleaner code. The session concludes with hands-on exploration
of diverse examples, providing practical insights into effectively implementing custom hooks
.
Note: Today’s lecture notes are comprehensive, surpassing the depth of previous lectures. Consider them an all-inclusive resource for a thorough understanding and effective utilization of Custom Hooks in your React applications.
A Few Concepts Before We Begin
1] Ternary Operator
The ternary operator, also known as the conditional operator, is a concise way to write an if-else
statement in a single line. It has the following syntax:
condition ? expressionIfTrue : expressionIfFalse;
Here’s how it works:
- The
condition
is evaluated. If it istrue
, the expression before the:
(colon) is executed; otherwise, the expression after the:
is executed.
Let’s look at a simple example:
const isRaining = true;
const weatherMessage = isRaining ? "Bring an umbrella" : "Enjoy the sunshine";
console.log(weatherMessage);
In this example, if isRaining
is true
, the weatherMessage
will be set to “Bring an umbrella”; otherwise, it will be set to “Enjoy the sunshine.”
The ternary operator is often used for simple conditional assignments, making the code more concise. However, it’s important to use it judiciously, as overly complex ternary expressions can reduce code readability.
2] Lifecycle Events
Lifecycle events in React represent various phases a component goes through from its birth to its removal from the DOM. These events are associated with class components and provide developers with the ability to execute code at specific points during a component’s existence.
It is completely okay if you find this section challenging at the moment, as we delve into a more in-depth discussion in the latter part of today’s notes. For now, aim to grasp an overview of the concepts introduced and the associated terminology.
The key lifecycle events in a class-based React component are:
componentDidMount
: This method is called after a component has been rendered to the DOM. It is commonly used to perform initial setup, data fetching, or subscriptions.
componentDidMount() { // Perform setup or data fetching here}
componentDidUpdate
: This method is invoked immediately after an update occurs. It’s useful for reacting to prop or state changes and performing additional actions.
componentDidUpdate(prevProps, prevState) { // Perform actions based on prop or state changes}
componentWillUnmount
: This method is called just before a component is removed from the DOM. It’s suitable for cleanup tasks, such as removing event listeners or canceling subscriptions.
componentWillUnmount() { // Clean up (e.g., remove event listeners or cancel subscriptions)}
With the introduction of React Hooks, functional components also gained lifecycle-like behavior through the useEffect
hook. The equivalent hooks are:
useEffect
with an empty dependency array: Equivalent tocomponentDidMount
. Runs after the initial render.
useEffect(() => { // Code to run after initial render}, []);
useEffect
with dependencies: Equivalent tocomponentDidUpdate
. Runs whenever the specified dependencies change.
useEffect(() => { // Code to run when dependencies change}, [dependency1, dependency2]);
useEffect
with a cleanup function: Equivalent tocomponentWillUnmount
. Runs before the component is unmounted.
useEffect(() => { // Code to run on component mount
return () => { // Cleanup code (similar to componentWillUnmount) };}, []);
These lifecycle events are crucial for managing side effects, updating UI in response to changes, and maintaining clean-up procedures for optimal application performance.
Well as emphasized before it is completely okay if you found the above section challenging at the moment, as we delve into a more in-depth discussion in the latter part of today’s notes. For now, aim to grasp an overview of the concepts introduced and the associated terminology.
3] Understanding Debouncing
Debouncing is a programming practice used to ensure that time-consuming tasks do not fire so often, making them more efficient. In the context of onInput
events, debouncing is often applied to delay the execution of certain actions (e.g., sending requests) until after a user has stopped typing for a specific duration.
Implementation:
The following example demonstrates debouncing in the onInput
event to delay the execution of a function that sends a request based on user input.
<html> <body> <!-- Input field with onInput event and debouncing --> <input id="textInput" type="text" onInput="debounce(handleInput, 500)" placeholder="Type something...">
<!-- Display area for the debounced input value --> <p id="displayText"></p>
<script> // Debounce function to delay the execution of a function function debounce(func, delay) { let timeoutId;
return function() { // Clear the previous timeout clearTimeout(timeoutId);
// Set a new timeout timeoutId = setTimeout(() => { func.apply(this, arguments); }, delay); }; }
// Function to handle the debounced onInput event function handleInput() { // Get the input field's value const inputValue = document.getElementById("textInput").value;
// Display the input value in the paragraph document.getElementById("displayText").innerText = "You typed: " + inputValue;
// Simulate sending a request (replace with actual AJAX call) console.log("Request sent:", inputValue); } </script> </body></html>
Explanation:
- The
debounce
function is a generic debounce implementation that takes a function (func
) and a delay time (delay
). - Inside the
debounce
function, a timeout is set to delay the execution of the provided function (func
) by the specified delay time (delay
). - The
handleInput
function is the actual function to be executed when theonInput
event occurs. It simulates sending a request (e.g., an AJAX call) based on user input.
How it works:
- When a user types in the input field, the
onInput
event triggers thedebounce
function. - The
debounce
function sets a timeout, and if the user continues typing within the specified delay time, the previous timeout is cleared, and a new one is set. - After the user stops typing for the specified delay, the
handleInput
function is executed.
This ensures that the function associated with the
onInput
event is not called on every keystroke but rather after the user has stopped typing for a brief moment, reducing unnecessary and potentially resource-intensive calls, such as sending requests.
Class Components vs Functional Components
State Management
Let us take a look at the implementation of a simple counter component using both functional and class-based approaches in React, emphasizing state management.
Using Class Component
import React from 'react';
class MyComponent extends React.Component { // Constructor to initialize state constructor(props) { super(props); this.state = { count: 0 }; }
// Method to increment the count when the button is clicked incrementCount = () => { // Updating the count state using this.setState this.setState({ count: this.state.count + 1 }); }
// Render method to display the current count and the "Increment" button render() { return ( <div> <p>{this.state.count}</p> <button onClick={this.incrementCount}>Increment</button> </div> ); }}
- In the class-based component, state is initialized in the constructor using
this.state
. this.state = { count: 0 };
initializes thecount
state variable with an initial value of 0.- The
incrementCount
method increases thecount
state by 1 when the “Increment” button is clicked. - The
render
method displays the current count and a button that triggers theincrementCount
method.
Using Functional Component
import React, { useState } from 'react';
function MyComponent() { // Using the useState hook to manage state in a functional component const [count, setCount] = useState(0);
// Function to increment the count when the button is clicked const incrementCount = () => { // Updating the count state using the setCount function setCount(count + 1); };
// Rendering the component with the current count and an "Increment" button return ( <div> <p>{count}</p> <button onClick={incrementCount}>Increment</button> </div> );}
- The
useState
hook is used to declare state variables within functional components. const [count, setCount] = useState(0);
initializes thecount
state variable with an initial value of 0, andsetCount
is a function used to update thecount
state.- The
incrementCount
function increases thecount
state by 1 each time the “Increment” button is clicked. - The JSX returned by the component displays the current count and a button that triggers the
incrementCount
function.
Both implementations achieve the same result, showcasing different approaches to managing state in React components. The functional component uses the
useState
hook for a more concise and modern syntax, while the class-based component follows the traditional class syntax for state management. The choice between them often depends on personal preference and the specific requirements of the application.
Lifecycle Events
Now, let’s explore the implementation of how lifecycle events are handled in both class-based and functional components in React.
Using Class Component
import React from 'react';
class MyComponent extends React.Component { // componentDidMount: Invoked after the component is first mounted to the DOM componentDidMount() { // Perform setup or data fetching here console.log('Component is mounted to the DOM'); }
// componentWillUnmount: Invoked just before the component is unmounted and destroyed componentWillUnmount() { // Clean up (e.g., remove event listeners or cancel subscriptions) console.log('Component is about to be unmounted'); }
// render: Renders the UI of the component render() { return ( // Render UI <div> Component Lifecycle Events (Class-Based) </div> ); }}
componentDidMount
: Invoked after the component is first mounted to the DOM. It’s a suitable place for initial setup or data fetching operations.componentWillUnmount
: Invoked just before the component is unmounted and destroyed. It’s used for cleanup tasks, such as removing event listeners or canceling subscriptions.render
: The method responsible for rendering the UI of the component.
Using Functional Component
import React, { useEffect } from 'react';
function MyComponent() { // useEffect: Invoked after the component is mounted and reinvoked if dependencies change useEffect(() => { // Perform setup or data fetching here
// Cleanup function (similar to componentWillUnmount) return () => { console.log('Component is about to be unmounted (cleanup)'); // Cleanup code goes here }; }, []); // Empty dependency array ensures useEffect runs only on mount and unmount
// Render UI return ( <div> Component Lifecycle Events (Functional) </div> );}
useEffect
: Invoked after the component is mounted and reinvoked if dependencies change. It can be used for both setup and cleanup.- The empty dependency array (
[]
) ensures that theuseEffect
runs only on mount and unmount, simulating the behavior ofcomponentDidMount
andcomponentWillUnmount
. - The cleanup function within
useEffect
is invoked just before the component is unmounted.
In functional components, the
useEffect
hook is a versatile replacement for various lifecycle events in class-based components. It handles both mounting and unmounting scenarios, offering a more concise and expressive way to manage side effects.
Significance of Returning a Component from useEffect
In the provided code snippet, the we utilize the useEffect
hook along with the setInterval
function to toggle the state of the render
variable every 5 seconds. This, in turn, controls the rendering of the MyComponent
or an empty div
based on the value of render
. Let’s break down the significance of returning a component from useEffect
:
import React, { useEffect, useState } from 'react';import './App.css';
function App() { const [render, setRender] = useState(true);
useEffect(() => { // Toggle the state every 5 seconds const intervalId = setInterval(() => { setRender(r => !r); }, 5000);
// Cleanup function: Clear the interval when the component is unmounted return () => { clearInterval(intervalId); }; }, []);
return ( <> {render ? <MyComponent /> : <div></div>} </> );}
function MyComponent() { useEffect(() => { console.error("Component mounted");
// Cleanup function: Log when the component is unmounted return () => { console.log("Component unmounted"); }; }, []);
return <div> From inside MyComponent </div>;}
export default App;
Understanding the Code
- The
useEffect
hook is used to create a side effect (in this case, toggling therender
state at intervals) when the component mounts. - A cleanup function is returned within the
useEffect
, which will be executed when the component is unmounted. In this example, it clears the interval previously set bysetInterval
. - By toggling the
render
state, the component (MyComponent
or an emptydiv
) is conditionally rendered or unrendered, demonstrating the dynamic nature of component rendering. - The
return
statement within theuseEffect
ofMyComponent
is used to specify what should be rendered when the component is active, in this case, a simplediv
with the text “From inside MyComponent.”
In summary, the ability to return a cleanup function from
useEffect
is crucial for managing resources, subscriptions, or intervals created during the component’s lifecycle. It helps ensure proper cleanup when the component is no longer in use, preventing memory leaks or unintended behavior.
React Hooks
React Hooks are functions that allow functional components in React to have state and lifecycle features that were previously available only in class components. Hooks were introduced in React 16.8 to enable developers to use state and other React features without writing a class.
Using these hooks, developers can manage state, handle side effects, optimize performance, and create more reusable and readable functional components in React applications. Each hook serves a specific purpose, contributing to a more modular and maintainable codebase.
In previous lectures, specifically Week 6 —we have already covered in depth the most commonly used hooks provided to us by React: useEffect
, useMemo
, useCallback
, useRef
, useReducer
, useContext
, useLayoutEffect
Custom Hooks
What?
Custom Hooks in React are user-defined functions that encapsulate reusable logic and stateful behavior. They allow developers to extract and share common functionality across multiple components, promoting code reusability and maintaining cleaner and more modular code.
Why?
The need for custom hooks arises from the desire to avoid code duplication and create a clean separation of concerns. By encapsulating specific logic in a custom hook, you can isolate and organize the functionality related to a particular concern or feature. This not only makes your codebase more maintainable but also facilitates easier testing and debugging.
How?
Custom hooks solve the problem of sharing logic between components without the need for higher-order components or render props. They provide a mechanism to encapsulate complex behavior, making it easier to reason about and reuse across different parts of your application. Ultimately, custom hooks contribute to a more efficient and scalable React codebase.
Examples
- Data fetching hooks
- Browser functionality related hooks - useOnlineStatus , useWindowSize, useMousePosition
- Performance/Timer based - useInterval, useDebounce
Use Cases of Custom Hooks
1] Data Fetching Hooks
Data fetching hooks can be used to encapsulate all the logic to fetch the data from your backend
Now below is how our code looks before using custom hooks
import { useEffect, useState } from 'react'import axios from 'axios'
function App() { const [todos, setTodos] = useState([])
useEffect(() => { axios.get("https://sum-server.100xdevs.com/todos") .then(res => { setTodos(res.data.todos); }) }, [])
return ( <> {todos.map(todo => <Track todo={todo} />)} </> )}
function Track({ todo }) { return <div> {todo.title} <br /> {todo.description} </div>}
export default App
We will now see a step by step guide on how you can use custom hook for the above use case.
Step 1 - Converting the data fetching
bit to a custom hook
import { useEffect, useState } from 'react';import axios from 'axios';
// Custom hook for fetching todosfunction useTodos() { const [todos, setTodos] = useState([]);
useEffect(() => { // Fetching todos using Axios axios.get("<https://sum-server.100xdevs.com/todos>") .then(res => { setTodos(res.data.todos); }) }, []);
// Return the todos state return todos;}
// Main App componentfunction App() { // Using the custom hook to fetch todos const todos = useTodos();
return ( <> {/* Rendering Track component for each todo */} {todos.map(todo => <Track key={todo.id} todo={todo} />)} </> );}
// Track component for rendering individual todofunction Track({ todo }) { return ( <div> {todo.title} <br /> {todo.description} </div> );}
export default App;
Explanation:
- Custom Hook (
useTodos
):- It encapsulates the data fetching logic using
axios
within a custom hook. - Utilizes the
useState
anduseEffect
hooks to manage state and perform side effects respectively. - Returns the
todos
state, making it accessible in the component that uses this custom hook.
- It encapsulates the data fetching logic using
- Main App Component:
- Imports and uses the
useTodos
custom hook, fetching todos and storing them in thetodos
variable. - Maps over the
todos
array, rendering theTrack
component for each todo.
- Imports and uses the
- Track Component:
- Receives an individual
todo
as a prop and renders itstitle
anddescription
.
- Receives an individual
By creating a custom hook (
useTodos
), the data fetching logic is abstracted and can be easily reused across different components. This promotes a cleaner and more modular code structure.
Step 2 - Cleaning the hook to include a loading
parameter
What if you want to show a loader when the data is not yet fetched from the backend?
import { useEffect, useState } from 'react';import axios from 'axios';
// Custom hook for fetching todos with loading indicatorfunction useTodos() { const [loading, setLoading] = useState(true); const [todos, setTodos] = useState([]);
useEffect(() => { // Fetching todos using Axios axios.get("<https://sum-server.100xdevs.com/todos>") .then(res => { setTodos(res.data.todos); setLoading(false); }) .catch(error => { console.error("Error fetching todos:", error); setLoading(false); }); }, []);
// Return todos and loading state return { todos: todos, loading: loading };}
// Main App componentfunction App() { // Using the custom hook to fetch todos const { todos, loading } = useTodos();
// Rendering loading message if data is still loading if (loading) { return <div>Loading...</div>; }
// Rendering Track component for each todo return ( <> {todos.map(todo => <Track key={todo.id} todo={todo} />)} </> );}
// Track component for rendering individual todofunction Track({ todo }) { return ( <div> {todo.title} <br /> {todo.description} </div> );}
export default App;
Explanation:
- Custom Hook (
useTodos
):- Introduces a
loading
state to track whether the data is still being fetched. - The
setLoading(false)
is placed in both the successful and error scenarios to handle loading completion. - Returns an object with both
todos
andloading
states.
- Introduces a
- Main App Component:
- Destructures the result from the custom hook, including
todos
andloading
. - If
loading
istrue
, it renders a loading message. Otherwise, it maps over thetodos
array and renders theTrack
component.
- Destructures the result from the custom hook, including
- Track Component:
- Remains the same, rendering individual todos.
By including a loading parameter in the custom hook, you can provide better user experience by displaying a loading message while the data is being fetched.
Step 3 - Auto refreshing hook
What if you want to keep polling the backend every n seconds? n
needs to be passed in as an input to the hook
import { useEffect, useState } from 'react';import axios from 'axios';
// Custom hook for fetching todos with auto-refreshfunction useTodos(n) { const [loading, setLoading] = useState(true); const [todos, setTodos] = useState([]);
// Function to fetch data from the backend function getData() { axios.get("<https://sum-server.100xdevs.com/todos>") .then(res => { setTodos(res.data.todos); setLoading(false); }) .catch(error => { console.error("Error fetching todos:", error); setLoading(false); }); }
useEffect(() => { // Initial data fetch getData();
// Set up interval to fetch data every n seconds const intervalId = setInterval(() => { getData(); }, n * 1000);
// Clean up the interval on component unmount or when n changes return () => clearInterval(intervalId); }, [n]);
// Return todos and loading state return { todos: todos, loading: loading };}
// Main App componentfunction App() { // Using the custom hook to fetch todos with auto-refresh every 5 seconds const { todos, loading } = useTodos(5);
// Rendering loading message if data is still loading if (loading) { return <div>Loading...</div>; }
// Rendering Track component for each todo return ( <> {todos.map(todo => <Track key={todo.id} todo={todo} />)} </> );}
// Track component for rendering individual todofunction Track({ todo }) { return ( <div> {todo.title} <br /> {todo.description} </div> );}
export default App;
Explanation:
- Custom Hook (
useTodos
):- Accepts an input
n
representing the interval in seconds for auto-refresh. - Utilizes the
getData
function to fetch data from the backend. - In the
useEffect
, sets up an interval to callgetData
everyn
seconds. - Cleans up the interval when the component unmounts or when
n
changes.
- Accepts an input
- Main App Component:
- Utilizes the custom hook with an interval of 5 seconds (
useTodos(5)
). - Renders a loading message if data is still loading and maps over
todos
otherwise.
- Utilizes the custom hook with an interval of 5 seconds (
- Track Component:
- Remains the same, rendering individual todos.
This step enhances the hook by adding an auto-refresh feature, ensuring the data is periodically fetched from the backend.
Step 4 - We Clear the Interval
import { useEffect, useState } from 'react';import axios from 'axios';
// Custom hook for fetching todos with auto-refreshfunction useTodos(n) { const [todos, setTodos] = useState([]); const [loading, setLoading] = useState(true);
useEffect(() => { // Set up interval to fetch data every n seconds const intervalId = setInterval(() => { axios.get("<https://sum-server.100xdevs.com/todos>") .then(res => { setTodos(res.data.todos); setLoading(false); }) .catch(error => { console.error("Error fetching todos:", error); setLoading(false); }); }, n * 1000);
// Initial data fetch axios.get("<https://sum-server.100xdevs.com/todos>") .then(res => { setTodos(res.data.todos); setLoading(false); }) .catch(error => { console.error("Error fetching todos:", error); setLoading(false); });
// Clean up the interval on component unmount or when n changes return () => clearInterval(intervalId); }, [n]);
// Return todos and loading state return { todos, loading };}
// Main App componentfunction App() { // Using the custom hook to fetch todos with auto-refresh every 10 seconds const { todos, loading } = useTodos(10);
// Rendering loading message if data is still loading if (loading) { return <div>Loading...</div>; }
// Rendering Track component for each todo return ( <> {todos.map(todo => <Track key={todo.id} todo={todo} />)} </> );}
// Track component for rendering individual todofunction Track({ todo }) { return ( <div> {todo.title} <br /> {todo.description} </div> );}
export default App;
Explanation:
- Custom Hook (
useTodos
):- Continues to have the auto-refresh functionality with a 10-second interval (
useTodos(10)
). - Clears the interval using
clearInterval
in the cleanup function returned byuseEffect
. This ensures that the interval is cleared when the component unmounts or whenn
changes.
- Continues to have the auto-refresh functionality with a 10-second interval (
- Main App Component:
- Remains the same, utilizing the custom hook and rendering todos.
- Track Component:
- Unchanged, rendering individual todos.
This step enhances the hook by adding a cleanup mechanism to clear the interval when it’s no longer needed, preventing potential memory leaks.
SWR Library
The swr
library is a powerful tool for data fetching in React applications. It simplifies the process of handling data fetching, caching, and re-fetching when needed. Here’s an explanation of the provided code snippet:
// Import the useSWR hook from the 'swr' libraryimport useSWR from 'swr';
// Define a fetcher function to handle data fetchingconst fetcher = async function(url) { // Fetch data from the specified URL const data = await fetch(url);
// Parse the response as JSON const json = await data.json();
// Return the parsed JSON data return json;};
// Example component using the useSWR hookfunction Profile() { // Use the useSWR hook to fetch data from the specified URL const { data, error, isLoading } = useSWR('<https://sum-server.100xdevs.com/todos>', fetcher);
// Handle different states: loading, error, and successful data fetch if (error) return <div>Failed to load</div>; if (isLoading) return <div>Loading...</div>;
// Render the component with the fetched data return <div>Hello, you have {data.todos.length} todos!</div>;}
Explanation:
- Importing
useSWR
:- The
useSWR
hook is imported from the ‘swr’ library. This hook simplifies data fetching by providing caching and re-fetching capabilities.
- The
- Fetcher Function:
- A
fetcher
function is defined to handle data fetching. It uses thefetch
API to retrieve data from the specified URL, parses the response as JSON, and returns the parsed data.
- A
- Usage in
Profile
Component:- The
useSWR
hook is used in theProfile
component to fetch data from the specified URL (https://sum-server.100xdevs.com/todos
). Thefetcher
function is provided as the second argument touseSWR
.
- The
- Handling Different States:
- The component checks for different states:
error
,isLoading
, and successful data fetch. Depending on the state, it renders appropriate content (error message, loading indicator, or the fetched data).
- The component checks for different states:
- Rendering Component:
- If the data is successfully fetched, the component renders a message indicating the number of todos.
Using
swr
can significantly simplify data fetching in React applications, providing a clean and efficient way to manage remote data. https://swr.vercel.app/
2] Browser Functionality Related Hooks
useOnlineStatus
The Custom React Hook — useIsOnline
determines whether the user is currently online or offline. It utilizes the window.navigator.onLine
property and event listeners to keep track of the online status. Here’s a detailed explanation:
1. useIsOnline
Hook:
import { useEffect, useState } from 'react';
function useIsOnline() { // Initialize state with the current online status const [isOnline, setIsOnline] = useState(window.navigator.onLine);
useEffect(() => { // Add event listeners to track online/offline changes const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false);
// Attach event listeners to the 'online' and 'offline' events window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline);
// Cleanup: Remove event listeners on component unmount return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []);
// Return the current online status return isOnline;}
Explanation:
- Initialization: The
isOnline
state variable is initialized with the current value ofwindow.navigator.onLine
. This represents the initial online status. - Effect Hook: The
useEffect
hook is used to add event listeners for the ‘online’ and ‘offline’ events when the component mounts. These listeners update theisOnline
state accordingly. - Event Listeners: Two event listeners,
handleOnline
andhandleOffline
, are defined to update theisOnline
state based on the user’s online or offline status. - Cleanup: The
useEffect
hook also returns a cleanup function. This function removes the event listeners when the component is unmounted, preventing memory leaks.
2. App
Component:
function App() { // Use the custom hook to get the current online status const isOnline = useIsOnline();
// Render different messages based on the online status return ( <> {isOnline ? "You are online, yay!" : "You are not online"} </> );}
export default App;
Explanation:
- The
App
component uses theuseIsOnline
hook to determine the current online status. - Based on the online status, it renders different messages to inform the user whether they are online or offline.
This custom hook provides a reusable way to track the user’s online status throughout the lifecycle of a React component.
useMousePosition
The Custom React hook —useMousePointer
allows tracking the current position of the mouse pointer. It utilizes the window.addEventListener
method with the ‘mousemove’ event to update the mouse position. Here’s a detailed explanation:
1. useMousePointer
Hook:
import { useEffect, useState } from 'react';
const useMousePointer = () => { // Initialize state with the initial mouse position (0, 0) const [position, setPosition] = useState({ x: 0, y: 0 });
// Event handler to update the mouse position on mouse movement const handleMouseMove = (e) => { setPosition({ x: e.clientX, y: e.clientY }); };
useEffect(() => { // Add event listener for 'mousemove' event when the component mounts window.addEventListener('mousemove', handleMouseMove);
// Cleanup: Remove event listener on component unmount return () => { window.removeEventListener('mousemove', handleMouseMove); }; }, []);
// Return the current mouse position return position;};
Explanation:
- Initialization: The
position
state variable is initialized with the initial mouse position ({ x: 0, y: 0 }
). - Event Handler: The
handleMouseMove
function is defined to update theposition
state with the current mouse coordinates (e.clientX
ande.clientY
) when the mouse is moved. - Effect Hook: The
useEffect
hook is used to add an event listener for the ‘mousemove’ event when the component mounts. This listener triggers thehandleMouseMove
function on mouse movement. - Cleanup: The
useEffect
hook returns a cleanup function that removes the ‘mousemove’ event listener when the component is unmounted, preventing memory leaks.
2. App
Component:
function App() { // Use the custom hook to get the current mouse position const mousePointer = useMousePointer();
// Render a message displaying the current mouse position return ( <> Your mouse position is {mousePointer.x} {mousePointer.y} </> );}
export default App;
Explanation:
- The
App
component utilizes theuseMousePointer
hook to obtain the current mouse position. - It renders a message displaying the x and y coordinates of the mouse position in real-time.
This custom hook provides an easy way to track and utilize the mouse pointer position within a React component.
3] Performance/Timer Based
useInterval
The Custom React Hook —useInterval
facilitates running a callback function at specified intervals. This hook is then utilized in the App
component to increment a timer every second. Here’s an in-depth explanation:
1. useInterval
Hook:
import { useEffect } from 'react';
const useInterval = (callback, delay) => { useEffect(() => { // Set up an interval and store the interval ID const intervalId = setInterval(callback, delay);
// Cleanup: Clear the interval when the component is unmounted return () => clearInterval(intervalId); }, [callback, delay]);};
Explanation:
- Function Signature: The
useInterval
hook takes two parameters -callback
(the function to be executed) anddelay
(the interval in milliseconds). - Effect Hook: Inside the
useEffect
hook,setInterval
is used to repeatedly call thecallback
function at the specifieddelay
. - Cleanup: The returned cleanup function ensures that the interval is cleared when the component using this hook is unmounted, preventing memory leaks.
2. App
Component:
import { useState } from 'react';import useInterval from './useInterval'; // Import the custom useInterval hook
function App() { // State to store the count value const [count, setCount] = useState(0);
// Utilize the useInterval hook to increment the count every second useInterval(() => { setCount(c => c + 1); }, 1000);
// Render the current count value return ( <> Timer is at {count} </> );}
export default App;
Explanation:
- The
App
component utilizes theuseInterval
hook to increment thecount
state value every second. - The rendered output displays the current value of the timer, which increases every second.
This custom hook simplifies the implementation of interval-based functionality in React components, providing a reusable and clean solution.
useDebounce
The Custom React Hook —useDebounce
is utilized in a SearchBar
component to debounce the user input, making it ideal for scenarios such as live search functionality. Below is a detailed explanation:
1. useDebounce
Hook:
import { useState, useEffect } from 'react';
const useDebounce = (value, delay) => { // State to store the debounced value const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => { // Set up a timer to update the debounced value after the specified delay const timerId = setTimeout(() => { setDebouncedValue(value); }, delay);
// Clean up the timer if the value changes before the delay has passed return () => clearTimeout(timerId); }, [value, delay]);
return debouncedValue;};
Explanation:
- Function Signature: The
useDebounce
hook takes two parameters -value
(the input value to be debounced) anddelay
(the debounce delay in milliseconds). - State: The
debouncedValue
state holds the debounced value. - Effect Hook: Inside the
useEffect
hook, a timer is set usingsetTimeout
. This timer updates thedebouncedValue
with the current input value after the specified delay. - Cleanup: The
clearTimeout
function is used for cleanup to ensure that the timer is cleared if the input value changes before the delay has passed. - Dependencies: The effect hook depends on the
value
anddelay
parameters, ensuring the effect is re-run when they change.
2. SearchBar
Component:
import React, { useState } from 'react';import useDebounce from './useDebounce';
const SearchBar = () => { // State to manage the user input const [inputValue, setInputValue] = useState('');
// Use the useDebounce hook to get the debounced value const debouncedValue = useDebounce(inputValue, 500); // 500 milliseconds debounce delay
// Integrate the debouncedValue in your component logic (e.g., trigger a search API call via a useEffect)
return ( <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="Search..." /> );};
export default SearchBar;
Explanation:
- The
SearchBar
component uses theuseDebounce
hook to get the debounced value of the user input (inputValue
). - The
onChange
handler updates theinputValue
state as the user types. - The debounced value (
debouncedValue
) can be further integrated into the component logic, such as triggering a search API call via auseEffect
.
This custom hook allows for efficient handling of debounced values, reducing the number of API calls or other expensive operations triggered by frequent user input changes.