React

Understanding React Hooks: A Complete Guide

Dinesh SutiharJanuary 25, 202615 min read
ReactHooksJavaScriptFrontend
Understanding React Hooks: A Complete Guide

Introduction to Hooks

Hooks were introduced in React 16.8 to allow functional components to use state and lifecycle features. They provide a more elegant and reusable way to share logic between components compared to class components.

useState - Managing State

useState is the most fundamental hook. It allows you to add state to functional components. Each call to useState creates an independent piece of state.

tsx
import { useState } from 'react';
 
function Counter() {
// Declare state variable with initial value
const [count, setCount] = useState(0);
// Functional update for state based on previous value
const increment = () => setCount(prev => prev + 1);
// Object state example
const [user, setUser] = useState({ name: '', email: '' });
const updateName = (name: string) => {
setUser(prev => ({ ...prev, name }));
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}

useEffect - Side Effects

useEffect handles side effects like data fetching, subscriptions, and DOM manipulation. It runs after render and can optionally clean up before the next effect or unmount.

tsx
import { useState, useEffect } from 'react';
 
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Effect function
async function fetchUser() {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
setLoading(false);
}
fetchUser();
// Cleanup function (optional)
return () => {
// Cancel pending requests, unsubscribe, etc.
};
}, [userId]); // Dependency array - effect runs when userId changes
if (loading) return <div>Loading...</div>;
return <div>{user?.name}</div>;
}

useMemo & useCallback - Optimization

These hooks help optimize performance by memoizing values and functions. Use them when you have expensive computations or when passing callbacks to child components.

tsx
import { useMemo, useCallback } from 'react';
 
function ExpensiveList({ items, filter }) {
// Memoize expensive computation
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.includes(filter));
}, [items, filter]); // Only recompute when items or filter changes
// Memoize callback function
const handleClick = useCallback((id: string) => {
console.log('Clicked:', id);
}, []); // Empty deps = function never changes
return (
<ul>
{filteredItems.map(item => (
<ListItem key={item} onClick={handleClick} />
))}
</ul>
);
}

useRef - Mutable References

useRef creates a mutable reference that persists across renders without causing re-renders when changed. Perfect for DOM references and storing previous values.

tsx
import { useRef, useEffect } from 'react';
 
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
const renderCount = useRef(0);
useEffect(() => {
// Focus input on mount
inputRef.current?.focus();
renderCount.current += 1;
});
return (
<div>
<input ref={inputRef} type="text" />
<p>Renders: {renderCount.current}</p>
</div>
);
}

Custom Hooks - Reusable Logic

Custom hooks let you extract component logic into reusable functions. They must start with 'use' and can call other hooks.

tsx
// Custom hook for fetching data
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
setLoading(true);
const res = await fetch(url, { signal: controller.signal });
const json = await res.json();
setData(json);
} catch (e) {
if (e.name !== 'AbortError') setError(e);
} finally {
setLoading(false);
}
}
fetchData();
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
 
// Usage
function Profile() {
const { data, loading } = useFetch('/api/profile');
if (loading) return <div>Loading...</div>;
return <div>{data?.name}</div>;
}

Rules of Hooks

  • Only call hooks at the top level (not inside loops, conditions, or nested functions)
  • Only call hooks from React functions (components or custom hooks)
  • Custom hooks must start with 'use'
  • Hooks are called in the same order every render

Conclusion

React Hooks have transformed how we write React applications. By understanding each hook's purpose and following best practices, you can write cleaner, more maintainable code. Start with useState and useEffect, then gradually incorporate optimization hooks as needed.

Share this article

Related Articles