22 Creating Custom Hooks
Custom hooks are just regular functions that use other hooks inside them. Think of them like extracting reusable logic into utility functions, but for stateful logic.
22.1 The Pattern
Custom Hook Naming Convention:
- Must start with “use”
- Example: useCounter, useFetch, useLocalStorage
22.2 Example: useCounter
Hook
Let’s build a reusable counter hook:
// useCounter.ts
import { useState } from 'react';
function useCounter(initialValue: number = 0) {
const [count, setCount] = useState(initialValue);
// Custom logic encapsulated
const increment = () => setCount(c => c + 1);
const decrement = () => setCount(c => c - 1);
const reset = () => setCount(initialValue);
// Return whatever you want to expose
return {
,
count,
increment,
decrement
reset;
}
}
export default useCounter;
22.2.1 Using the Custom Hook
// CounterComponent.tsx
import useCounter from './useCounter';
function CounterComponent() {
// Use it like any other hook!
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
;
) }
22.3 Flutter Analogy
This is similar to creating a mixin or a controller class in Flutter:
Flutter Pattern:
┌────────────────────────────────┐
│ class CounterController { │
│ int count = 0; │
│ void increment() {...} │
│ void decrement() {...} │
│ } │
└────────────────────────────────┘
↓ Similar to ↓
┌────────────────────────────────┐
│ function useCounter() { │
│ const [count, setCount]... │
│ const increment = () => ... │
│ return { count, increment } │
│ } │
└────────────────────────────────┘
22.4 More Practical Example: useLocalStorage
This hook persists state to localStorage (like Flutter’s SharedPreferences):
// useLocalStorage.ts
import { useState, useEffect } from 'react';
function useLocalStorage<T>(key: string, initialValue: T) {
// Get from localStorage or use initial value
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
catch (error) {
} console.error(error);
return initialValue;
};
})
// Update localStorage when value changes
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(storedValue));
catch (error) {
} console.error(error);
}, [key, storedValue]);
}
return [storedValue, setStoredValue] as const;
}
export default useLocalStorage;
22.4.1 Usage
function UserProfile() {
// Persists automatically! 🎉
const [name, setName] = useLocalStorage('username', 'Guest');
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<div>
<input
={name}
value={(e) => setName(e.target.value)}
onChange/>
<p>Theme: {theme}</p>
</div>
;
) }
22.5 Structure of a Custom Hook
Anatomy of a Custom Hook:
┌─────────────────────────────────────┐
│ function useSomething() { │
│ │
│ 1. Use other hooks │
│ useState, useEffect, etc. │
│ │
│ 2. Add custom logic │
│ Helper functions, calculations │
│ │
│ 3. Return values/functions │
│ Object or array │
│ │
│ } │
└─────────────────────────────────────┘
22.6 Key Points
- Must follow hook rules - Only call at top level, only in React functions
- Name starts with “use” - Convention that React relies on
- Can use any hooks inside - useState, useEffect, other custom hooks
- Return anything - Objects, arrays, single values
- Reusable across components - Share stateful logic