- React Tutorial
- React Home
- React Setup
- React Introduction
- React ES6
- React Render HTML
- React JSX
- React Components
- React Class
- React Props
- React Events
- React Conditional
- React Lists
- React Forms
- React Router
- React Memo
- React CSS Styling
- React Hooks
- What is a Hook?
- React useState
- React useEffect
- React useContext
- React useRef
- React useReducer
- React useCallback
- React useMemo
- React Custom Hooks
React Custom Hooks
- Custom hooks are essentially JavaScript functions that start with the prefix "use". They allow you to extract component logic into reusable functions, keeping your UI code clean and focused on what to display rather than how to handle data.
- By leveraging existing hooks like
useStateanduseEffectinside your own functions, you can create a specialized toolkit of logic that can be shared across your entire application.
Purpose
- Reusability: If you find yourself writing the same
useEffectoruseStatelogic in three different components, a custom hook is the solution. It follows the DRY (Don't Repeat Yourself) principle. - Maintainability: When logic is isolated in a single hook, fixing a bug or adding a feature to that logic happens in one file rather than hunted down in ten different components.
- Abstraction: Large components are hard to read. Moving complex data fetching or form validation into a custom hook makes your main component code significantly shorter and easier to understand for your team.
Best Practice: Use custom hooks to separate "logic" from "view." Your component should ideally handle what the user sees, while the hook handles how the data changes.
Syntax
- Creating a custom hook is straightforward: you write a regular function, but you follow the Rules of Hooks (don't call hooks inside loops or conditions).
- Always start the function name with
use. This isn't just a naming convention; it allows React's linter to check if you are using hooks correctly.
import { useState, useEffect } from 'react';
// The "use" prefix is mandatory for the React linter
const useCustomHook = (initialValue) => {
const [state, setState] = useState(initialValue);
useEffect(() => {
// Example: Subscribing to an external API or setting up a timer
console.log('Hook initialized');
return () => {
// Clean up logic to prevent memory leaks
console.log('Hook cleaned up');
};
}, []); // Empty dependency array means this runs once on mount
// You can return anything: an array, an object, or a single value
return [state, setState];
};
Common Mistake: Forgetting to start your hook name with "use". If you name it
handleStorage instead of useStorage, React won't be able to track state updates correctly, and you won't get warnings about hook violations.
UseLocalStorage Hook
- Storing data in
localStorageis a common task. However, keeping that storage in sync with your React state can be tedious. This hook automates that synchronization.
import { useState, useEffect } from 'react';
const useLocalStorage = (key, initialValue) => {
// Get initial state from local storage or use the provided initialValue
const [value, setValue] = useState(() => {
try {
const storedValue = localStorage.getItem(key);
return storedValue ? JSON.parse(storedValue) : initialValue;
} catch (error) {
console.error("Error reading localStorage key:", key, error);
return initialValue;
}
});
// Update local storage whenever the key or value changes
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error("Error setting localStorage key:", key, error);
}
}, [key, value]);
return [value, setValue];
};
Usage
// Use it exactly like useState, but the value persists on page refresh!
const [theme, setTheme] = useLocalStorage('app-theme', 'light');
Watch Out: LocalStorage only stores strings. That's why we use
JSON.stringify and JSON.parse. Also, remember that LocalStorage is synchronous and can block the main thread if you store massive amounts of data.
useToggle Hook
- One of the most frequent state changes in React is switching a boolean between
trueandfalse(e.g., opening a modal, showing a dropdown, or expanding a menu).
import { useState, useCallback } from 'react';
const useToggle = (initialState = false) => {
const [isToggled, setIsToggled] = useState(initialState);
// We wrap the toggle function in useCallback to prevent
// unnecessary re-renders in child components
const toggle = useCallback(() => {
setIsToggled((prev) => !prev);
}, []);
return [isToggled, toggle];
};
Usage
const [isModalOpen, toggleModal] = useToggle(false);
return (
<button onClick={toggleModal}>
{isModalOpen ? 'Close Modal' : 'Open Modal'}
</button>
);
Developer Tip: The
useToggle hook is a classic "Swiss Army Knife" utility. It’s perfect for handling Navigation menus, Accordions, and Password visibility toggles.
UseMediaQuery Hook
- While CSS media queries handle styling, you often need to change logic or components based on screen size (e.g., rendering a MobileNav vs. a DesktopNav).
import { useState, useEffect } from 'react';
const useMediaQuery = (query) => {
const [matches, setMatches] = useState(false);
useEffect(() => {
const mediaQueryList = window.matchMedia(query);
// Set the initial value
setMatches(mediaQueryList.matches);
// Modern way to listen for changes
const listener = (event) => setMatches(event.matches);
mediaQueryList.addEventListener('change', listener);
return () => {
// Clean up the listener when the component unmounts
mediaQueryList.removeEventListener('change', listener);
};
}, [query]);
return matches;
};
Usage
const isMobile = useMediaQuery('(max-width: 768px)');
return (
<div>
{isMobile ? <MobileSidebar /> : <DesktopSidebar />}
</div>
);
Best Practice: Always clean up event listeners in your
useEffect return function. Failing to do so can cause "memory leaks," where your app gets slower over time as it tries to run listeners for components that are no longer on the screen.
Summary
Custom hooks aren't just a "nice-to-have" feature; they are the backbone of modern React development. By encapsulating logic into hooks like useLocalStorage or useMediaQuery, you create a codebase that is modular, testable, and significantly easier to read. As your application grows, look for patterns in your components any logic that appears twice is a prime candidate for a custom hook.