React Hooks: A Complete Guide for Beginners
React Hooks revolutionized how we write React components. Instead of using class components for state and lifecycle methods, we can now use functional components with hooks. Let’s dive into the most important hooks you need to know.
Why Hooks?
Before hooks, you had to convert functional components to class components just to add state. Hooks let you use state and other React features without writing classes.
Benefits:
- Simpler, more readable code
- Easier to share stateful logic between components
- No
thiskeyword confusion - Better code organization
useState: Adding State
The most basic hook for adding state to functional components:
```javascript import { useState } from ‘react’;
function Counter() { const [count, setCount] = useState(0);
return (
Count: {count}
<button onClick={() => setCount(count + 1)}> IncrementKey points:
useStatereturns an array: [current value, setter function]- The argument to
useStateis the initial state - You can have multiple state variables
Multiple State Variables
```javascript function UserForm() { const [name, setName] = useState(”); const [email, setEmail] = useState(”); const [age, setAge] = useState(0);
return (
); } ```State with Objects
```javascript function UserForm() { const [user, setUser] = useState({ name: ”, email: ”, age: 0 });
const updateField = (field, value) => { setUser(prev => ({ …prev, [field]: value })); };
return ( <input value={user.name} onChange={(e) => updateField(‘name’, e.target.value)} /> ); } ```
useEffect: Side Effects
Handle side effects like data fetching, subscriptions, or manually changing the DOM:
```javascript import { useState, useEffect } from ‘react’;
function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true);
useEffect(() => { setLoading(true); fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => { setUser(data); setLoading(false); }); }, [userId]); // Re-run when userId changes
if (loading) return
Cleanup Functions
```javascript useEffect(() => { const subscription = api.subscribe(data => { setData(data); });
// Cleanup function return () => { subscription.unsubscribe(); }; }, []); ```
Effect Dependencies
```javascript // Run once on mount useEffect(() => { console.log(‘Component mounted’); }, []);
// Run on every render useEffect(() => { console.log(‘Component rendered’); });
// Run when specific values change useEffect(() => { console.log(‘Count changed’); }, [count]); ```
useContext: Sharing Data
Access context values without prop drilling:
```javascript import { createContext, useContext, useState } from ‘react’;
const ThemeContext = createContext();
function ThemeProvider({ children }) { const [theme, setTheme] = useState(‘light’);
return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); }
function ThemedButton() { const { theme, setTheme } = useContext(ThemeContext);
return ( <button style={{ background: theme === ‘light’ ? ‘#fff’ : ‘#000’ }} onClick={() => setTheme(theme === ‘light’ ? ‘dark’ : ‘light’)} > Toggle Theme ); } ```
useRef: Accessing DOM Elements
Create mutable references that persist across renders:
```javascript import { useRef, useEffect } from ‘react’;
function AutoFocusInput() { const inputRef = useRef(null);
useEffect(() => { inputRef.current.focus(); }, []);
return ; } ```
Storing Mutable Values
```javascript function Timer() { const [count, setCount] = useState(0); const intervalRef = useRef(null);
const start = () => { intervalRef.current = setInterval(() => { setCount(c => c + 1); }, 1000); };
const stop = () => { clearInterval(intervalRef.current); };
return (
{count}
useMemo: Performance Optimization
Memoize expensive calculations:
```javascript import { useMemo, useState } from ‘react’;
function ExpensiveComponent({ items }) { const [filter, setFilter] = useState(”);
const filteredItems = useMemo(() => { console.log(‘Filtering items…’); return items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()) ); }, [items, filter]); // Only recalculate when these change
return (
useCallback: Memoizing Functions
Prevent unnecessary re-renders by memoizing callback functions:
```javascript import { useCallback, useState } from ‘react’;
function TodoList() { const [todos, setTodos] = useState([]);
const addTodo = useCallback((text) => { setTodos(prev => […prev, { id: Date.now(), text }]); }, []); // Function identity stays the same
return
Custom Hooks: Reusable Logic
Create your own hooks to share stateful logic:
```javascript function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { setLoading(true); fetch(url) .then(res => res.json()) .then(data => { setData(data); setLoading(false); }) .catch(err => { setError(err); setLoading(false); }); }, [url]);
return { data, loading, error }; }
// Usage function UserProfile({ userId }) { const { data, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return
Rules of Hooks
Important rules you must follow:
- Only call hooks at the top level (not inside loops, conditions, or nested functions)
- Only call hooks from React function components or custom hooks
- Name custom hooks with “use” prefix
```javascript // ❌ Wrong function Component() { if (condition) { const [state, setState] = useState(0); // Don’t do this! } }
// ✅ Correct function Component() { const [state, setState] = useState(0);
if (condition) { // Use the state here } } ```
Common Patterns
Form Handling
```javascript function useForm(initialValues) { const [values, setValues] = useState(initialValues);
const handleChange = (e) => { setValues({ …values, [e.target.name]: e.target.value }); };
const reset = () => setValues(initialValues);
return { values, handleChange, reset }; }
// Usage function LoginForm() { const { values, handleChange, reset } = useForm({ email: ”, password: ” });
return (
); } ```Local Storage
```javascript function useLocalStorage(key, initialValue) { const [value, setValue] = useState(() => { const item = localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; });
useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]);
return [value, setValue]; }
// Usage function App() { const [theme, setTheme] = useLocalStorage(‘theme’, ‘light’);
return ( <button onClick={() => setTheme(theme === ‘light’ ? ‘dark’ : ‘light’)}> Current theme: {theme} ); } ```
Best Practices
- Keep hooks at the top level of your component
- Use descriptive names for state variables
- Split complex state into multiple useState calls
- Extract reusable logic into custom hooks
- Use useCallback and useMemo sparingly (only when needed)
- Clean up effects to prevent memory leaks
Next Steps
- Practice by converting class components to hooks
- Build custom hooks for common patterns
- Learn about useReducer for complex state logic
- Explore React Query for data fetching
- Check out the React Hooks documentation
Hooks make React development more enjoyable and productive. Start using them in your projects today!