Custom hooks let you extract component logic into reusable functions.
Custom hooks are JavaScript functions that start with "use" and may call other hooks. They let you extract component logic into reusable functions.
// Custom hooks always start with "use"
function useCustomHook() {
// You can use other hooks here
const [state, setState] = useState(initialState);
// Custom logic goes here
// Return values that will be used by components
return { state, setState };
}A common use case for custom hooks is form handling. Here's a custom hook for managing form state.
import { useState } from 'react';
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value
});
};
const handleSubmit = (callback) => (e) => {
e.preventDefault();
setIsSubmitting(true);
callback();
};
const resetForm = () => {
setValues(initialValues);
setErrors({});
};
return {
values,
errors,
isSubmitting,
handleChange,
handleSubmit,
resetForm,
setValues,
setErrors
};
}
// Using the custom hook in a component
function SignupForm() {
const { values, handleChange, handleSubmit, resetForm } = useForm({
email: '',
password: ''
});
const submitForm = () => {
console.log('Form submitted with:', values);
// API call would go here
resetForm();
};
return (
<form onSubmit={handleSubmit(submitForm)}>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
placeholder="Email"
/>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
placeholder="Password"
/>
<button type="submit">Sign Up</button>
</form>
);
}This custom hook makes it easy to persist state in localStorage.
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
// Get stored value from localStorage or use initialValue
const getStoredValue = () => {
if (typeof window === 'undefined') {
return initialValue;
}
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error('Error reading from localStorage:', error);
return initialValue;
}
};
// State to store our value
const [storedValue, setStoredValue] = useState(getStoredValue);
// Return a wrapped version of useState's setter function that
// persists the new value to localStorage
const setValue = (value) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to localStorage
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
console.error('Error writing to localStorage:', error);
}
};
// Sync with localStorage if the key changes
useEffect(() => {
setStoredValue(getStoredValue());
}, [key]);
return [storedValue, setValue];
}
// Using the custom hook in a component
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}This hook makes it easy to respond to media queries in your components.
import { useState, useEffect } from 'react';
function useMediaQuery(query) {
const [matches, setMatches] = useState(false);
useEffect(() => {
// Create a media query list
const mediaQuery = window.matchMedia(query);
// Set the initial value
setMatches(mediaQuery.matches);
// Define a callback function to handle changes
const handleChange = (event) => {
setMatches(event.matches);
};
// Add the callback as a listener for changes to the media query
mediaQuery.addEventListener('change', handleChange);
// Clean up
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, [query]);
return matches;
}
// Using the custom hook in a component
function ResponsiveComponent() {
const isMobile = useMediaQuery('(max-width: 768px)');
return (
<div>
<h1>{isMobile ? 'Mobile View' : 'Desktop View'}</h1>
{isMobile ? (
<div>Optimized for mobile</div>
) : (
<div>Full desktop experience</div>
)}
</div>
);
}A reusable hook for making API requests with loading and error states.
import { useState, useEffect, useCallback } from 'react';
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
}, [url, options]);
useEffect(() => {
fetchData();
}, [fetchData]);
// Return the state and a function to manually refetch
return { data, loading, error, refetch: fetchData };
}
// Using the custom hook in a component
function UserProfile({ userId }) {
const { data, loading, error, refetch } = useFetch(
`https://api.example.com/users/${userId}`
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!data) return null;
return (
<div>
<h1>{data.name}</h1>
<p>Email: {data.email}</p>
<button onClick={refetch}>Refresh</button>
</div>
);
}