The primary benefit of React Hooks is that they allow you to separate complex logic (like state management, data fetching, or setting up subscriptions) from the component’s UI rendering. Custom Hooks take this principle further, allowing you to extract and share this logic between entirely different components.
A Custom Hook is simply a JavaScript function whose name starts with the prefix use (e.g., useFetch, useLocalStorage).
1. Why Create Custom Hooks?
The core motivation is to achieve logic reuse.
- Avoid Duplication: If two components need the exact same non-visual logic (e.g., tracking the online status, fetching data on mount), a Custom Hook allows you to write that logic once.
- Separation of Concerns: Custom Hooks ensure that your component function (
App.js) remains focused on returning JSX, keeping the complex, stateful logic separated and testable. - Component-like Functionality: They provide component-like functionality (access to
useState,useEffect, etc.) without being a component themselves.
2. Rules for Creating Custom Hooks
- Naming: The function name must start with the word
use. - Calls: Custom Hooks can only call other Hooks (e.g.,
useState,useEffect, other Custom Hooks) and standard JavaScript functions. - No JSX: Custom Hooks do not return JSX markup. They return data, state, or functions (e.g.,
[value, setValue]).
3. Execution Example: Tracking Mouse Position
Let’s create a custom hook, useMousePosition, that handles the logic for attaching and removing an event listener to track the mouse coordinates, and then share this logic between two independent components.
A. useMousePosition.js (The Custom Hook)
Create a new file useMousePosition.js inside your src directory:
import { useState, useEffect } from 'react';
// The function name MUST start with 'use'
function useMousePosition() {
// 1. Define the state we want to manage and expose
const [position, setPosition] = useState({ x: 0, y: 0 });
// 2. Define the logic (side effect)
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
// Attach the event listener to the window
window.addEventListener('mousemove', handleMouseMove);
// 3. Provide a cleanup function to remove the listener
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []); // Empty array ensures the listeners are attached/removed only once
// 4. Return the data needed by the component
return position;
}
export default useMousePosition;
B. App.js (Consumption by Multiple Components)
Open App.js and use the new hook in two different locations:
import React from 'react';
import useMousePosition from './useMousePosition'; // Import the Custom Hook
// Component 1: Shows the position data
function MouseTracker() {
// Call the hook just like a built-in hook
const position = useMousePosition();
return (
<div style={{ padding: '20px', border: '1px solid gray' }}>
<h3>Mouse Tracker 1</h3>
<p>X: {position.x} | Y: {position.y}</p>
</div>
);
}
// Component 2: Also shows the position data
function AnotherTracker() {
const position = useMousePosition();
return (
<div style={{ padding: '20px', border: '1px solid blue', marginTop: '10px' }}>
<h3>Mouse Tracker 2</h3>
<p>Coordinates: ({position.x}, {position.y})</p>
</div>
);
}
function App() {
return (
<div className="CustomHookDemo">
<h1>Custom Hooks Demo</h1>
<MouseTracker />
<AnotherTracker />
</div>
);
}
export default App;
- Local Execution: Run
npm start. - Observation: Both
MouseTrackerandAnotherTrackerindependently gain the logic for tracking mouse position simply by calling theuseMousePosition()hook. This demonstrates that the logic was successfully reused without duplicating anyuseStateoruseEffectcode.
