Hello there! Having spent several years developing React applications, I've noticed a handful of functionalities that pop up again and again. They're not just limited to a single project, but are versatile enough to be used in various contexts. Recognizing and transforming these recurring functionalities into custom hooks can save us a ton of time and effort. Plus, these custom hooks are super adaptable and can be easily integrated into almost any project, no matter how big or small, or how simple or complex.
Let's dig a little deeper into these useful custom hooks and chat about how they can be practically applied:
- useDebounce: This handy hook lets us delay the processing of a function for a set amount of time. It's a lifesaver when we want to limit how often a function can trigger or run.
- useLocalStorageState: With this hook, we can easily save, retrieve, and play with data in the browser's local storage. It's a gem for keeping the state consistent across sessions or tabs.
- useWindowScroll: This nifty custom hook helps keep track of the window's scroll position. It's great for a range of uses like lazy loading images, infinite scrolling, or revealing/hiding elements based on the scroll position.
useDebounce
import { useEffect, useState } from "react";
export function useDebounce({
value,
delay,
}: {
value: string;
delay: number;
}) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
}
The useDebounce
is a custom React hook designed to delay the update of a state value until after a specified delay. This is useful for reducing the number of updates that occur in response to rapid or frequent changes to a value, such as user input in a text field. Let's break down how it works and provide an example of its usage.
How useDebounce
Works:
-
Input Parameters: It takes an object with two properties:
-
value
: The current value that you want to debounce. -
delay
: The amount of time (in milliseconds) you want to wait after the last change before updating the debounced value.
-
-
State: It initializes a state variable
debouncedValue
with the initialvalue
passed to the hook. -
Effect Hook: It uses the
useEffect
hook to perform side effects:-
Set Timeout: Inside
useEffect
, it sets a timeout that waits for the specifieddelay
before updatingdebouncedValue
to the currentvalue
. -
Cleanup Function: Returns a cleanup function that clears the timeout if the
value
ordelay
changes before the timeout completes. This prevents the debounced value from updating if the input value changes again within the delay period.
-
Set Timeout: Inside
-
Return: It returns the
debouncedValue
. This value reflects the latestvalue
passed to the hook, but only after the specifieddelay
has elapsed without any further changes to thevalue
.
Example Usage:
Imagine you have a search input field where you want to fetch search results from an API, but you want to minimize the number of API calls by only fetching the results once the user has stopped typing for 500 milliseconds.
import React, { useState } from 'react';
import { useDebounce } from './useDebounce';
function SearchComponent() {
const [inputValue, setInputValue] = useState('');
const debouncedSearchTerm = useDebounce({ value: inputValue, delay: 500 });
// Effect for API call
useEffect(() => {
if (debouncedSearchTerm) {
// Function to fetch search results
console.log(`Fetching search results for: ${debouncedSearchTerm}`);
// Here you would fetch your search results using debouncedSearchTerm
}
}, [debouncedSearchTerm]); // This effect runs only when debouncedSearchTerm changes
return (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Search..."
/>
);
}
In this example:
- The user types into the search input field.
- The
inputValue
state updates with each keystroke, but thedebouncedSearchTerm
only updates after the user has stopped typing for 500 milliseconds. - The
useEffect
hook listening todebouncedSearchTerm
then triggers, potentially fetching search results based on the debounced term.
This approach ensures that your application performs the search operation efficiently, reducing unnecessary API calls or processing when the user is typing fast.
useLocalStorageState
import { useState, useEffect, useMemo } from "react";
// Define a custom hook that syncs state with local storage
export const useLocalStorageState = <T>(
key: string,
initialValue: T
): [T, React.Dispatch<React.SetStateAction<T>>] => {
// Get the initial value from local storage, if available
const storedValue =
typeof window !== "undefined" ? localStorage.getItem(key) : null;
const initial = useMemo(() => {
try {
return storedValue ? JSON.parse(storedValue) : initialValue;
} catch (error) {}
}, [initialValue, storedValue]);
// Create the state using useState
const [state, setState] = useState<T>(initial);
// Update local storage whenever the state changes
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state));
}, [key, state]);
return [state, setState];
};
// Path: src/lib/hooks/use-sessionstorage-state.hook.ts
export const useSessionStorageState = <T>(
key: string,
initialValue: T
): [T, React.Dispatch<React.SetStateAction<T>>] => {
const storedValue =
typeof window !== "undefined" ? sessionStorage.getItem(key) : null;
const initial = useMemo(() => {
try {
return storedValue ? JSON.parse(storedValue) : initialValue;
} catch (error) {}
}, [initialValue, storedValue]);
const [state, setState] = useState<T>(initial);
useEffect(() => {
sessionStorage.setItem(key, JSON.stringify(state));
}, [key, state]);
return [state, setState];
};
The useLocalStorageState
function is a custom React hook designed to synchronize a component's state with the local storage of the browser. This allows the state to persist across browser sessions. Here's a breakdown of how it works and an example of how to use it.
How useLocalStorageState
Works:
-
Generics: It uses TypeScript generics (
<T>
) to allow for flexibility in the type of value it can handle (e.g., string, number, object). -
Input Parameters:
-
key
: A unique string that identifies the item in local storage. -
initialValue
: The initial value for the state if no value is found in local storage.
-
-
Retrieving Initial Value:
- Checks if the window object is available to ensure code doesn't break during server-side rendering (e.g., in Next.js applications).
- Attempts to get the stored value from local storage using the provided
key
. - Uses
useMemo
to parse the stored JSON value only once or fallback to theinitialValue
if parsing fails or if no value is found.
-
State Management:
- Initializes the state with either the parsed local storage value or the provided initial value.
-
Effect for Syncing with Local Storage:
- Utilizes
useEffect
to update the local storage item whenever the state changes. It serializes the state to a JSON string and stores it under the providedkey
.
- Utilizes
-
Return Values:
- Returns a tuple containing the current state and a setter function (
setState
), similar to the return value of theuseState
hook.
- Returns a tuple containing the current state and a setter function (
Example Usage:
Let's say you have a form input where you want to persist the user's input even if they refresh the page or close and reopen the browser.
import React from 'react';
import { useLocalStorageState } from './useLocalStorageState';
function FormComponent() {
const [name, setName] = useLocalStorageState('userName', '');
return (
<div>
<label htmlFor="name">Name:</label>
<input
id="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<p>Hello, {name}!</p>
</div>
);
}
In this example:
- The
useLocalStorageState
hook is used to bind the input field's value to a piece of state that is synchronized with local storage. Thekey
is'userName'
. - If the user enters their name and then refreshes the page, the input field will still display their name because it was saved to local storage.
- The state is initially set to the value retrieved from local storage if it exists; otherwise, it falls back to an empty string (
''
). - Any changes to the input field update both the state and the corresponding item in local storage, ensuring the two are always in sync.
This approach enhances the user experience by making the UI state persistent across sessions without requiring any backend infrastructure to store user data.
useWindowScroll
import { useEffect, useState } from "react";
interface WindowScrollState {
x: number;
y: number;
}
export const useWindowScroll = (): WindowScrollState => {
const [scrollPosition, setScrollPosition] = useState<WindowScrollState>({
x: typeof window !== "undefined" ? window.scrollX : 0,
y: typeof window !== "undefined" ? window.scrollY : 0,
});
useEffect(() => {
const handleScroll = () => {
setScrollPosition({
x: typeof window !== "undefined" ? window.scrollX : 0,
y: typeof window !== "undefined" ? window.scrollY : 0,
});
};
if (typeof window !== "undefined") {
// Attach the scroll event listener when the component mounts
window.addEventListener("scroll", handleScroll);
// Remove the scroll event listener when the component unmounts
return () => {
window.removeEventListener("scroll", handleScroll);
};
}
}, []);
return scrollPosition;
};
The useWindowScroll
function is a custom React hook designed to track and provide the window's scroll position. It encapsulates the logic for listening to scroll events and updating the state accordingly to reflect the current scroll position of the window. This can be particularly useful for features like showing or hiding elements based on scroll position, implementing scroll-based animations, or simply monitoring user scroll behavior. Let's break down its workings and illustrate its usage with an example.
How useWindowScroll
Works:
-
State Initialization: Initializes a state variable
scrollPosition
with an object that contains two properties:-
x
: Represents the horizontal scroll position. It's set towindow.scrollX
if thewindow
object is available, otherwise to0
. -
y
: Represents the vertical scroll position. It's set towindow.scrollY
if thewindow
object is available, otherwise to0
.
-
-
Effect Hook: Uses
useEffect
to set up side effects:-
Scroll Event Listener: Defines a function
handleScroll
that updatesscrollPosition
based on the current scroll position of the window. -
Event Listener Registration: If the
window
object is available, it addshandleScroll
as an event listener to the window'sscroll
event. This ensures the scroll position is updated whenever the user scrolls. -
Cleanup: Returns a cleanup function that removes the
scroll
event listener when the component using this hook unmounts, preventing potential memory leaks.
-
Scroll Event Listener: Defines a function
-
Return Value: Returns the current
scrollPosition
state, allowing components to access the latest scroll position as{ x, y }
.
Example Usage:
Consider a scenario where you want to display a "Back to Top" button only when the user has scrolled down a significant amount.
import React from 'react';
import { useWindowScroll } from './useWindowScroll';
function BackToTopButton() {
const { y } = useWindowScroll();
const handleBackToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
return (
<button
style={{
position: 'fixed',
bottom: '20px',
right: '20px',
display: y > 200 ? 'block' : 'none',
}}
onClick={handleBackToTop}
>
Back to Top
</button>
);
}
In this example:
- The
useWindowScroll
hook provides the vertical scroll position (y
) of the window. - The "Back to Top" button is conditionally rendered based on the scroll position. It appears only when the user has scrolled down more than 200 pixels.
- Clicking the button scrolls the window back to the top smoothly.
This approach enhances user navigation on long pages by providing an easily accessible means to return to the top of the page, improving the overall user experience.
Top comments (0)