useHooks - Mastering React Built-in Hooks
React provides a set of built-in Hooks that enable you to manage state, handle side effects, optimize performance, and share logic across components. This guide explores these Hooks, offering syntax explanations, examples, and best practices.
useState
useState is a React Hook that lets you add a state variable to your functional components.
Syntax
const [state, setState] = useState(initialState);
- initialState: The starting value of the state variable, which can be any type or a function.
- state: The current value of the state variable.
- setState: A function to update the state and trigger a re-render.
Key Features and Usage
-
Adding State to a Component
const [age, setAge] = useState(28); const [name, setName] = useState('Taylor'); const [todos, setTodos] = useState(() => createTodos());
-
Updating State Based on Previous State
setAge(prevAge => prevAge + 1);
-
Updating Objects and Arrays in State Always create a new copy of objects or arrays before updating them:
setTodos(prevTodos => [...prevTodos, newTodo]);
-
Avoiding Recreating Initial State Use an initializer function for performance optimization:
const [count, setCount] = useState(() => computeInitialCount());
Troubleshooting
- State Not Updating in Logs: React batches updates; logs might show stale data.
- "Too Many Re-renders" Error: Avoid setting state in the render cycle.
Example
const [name, setName] = useState('Edward');
function handleClick() {
setName('Taylor');
setAge(a => a + 1);
}
useReducer
useReducer is a React Hook that provides a more structured approach to managing state, especially for complex state logic.
Syntax
const [state, dispatch] = useReducer(reducer, initialState);
- reducer: A function that defines how the state changes based on an action.
- initialState: The starting value of the state.
- dispatch: A function to trigger state updates by dispatching actions.
Examples
Example: Simple Counter
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
function increment() {
dispatch({ type: 'increment' });
}
Additional Examples
-
Managing Forms
function reducer(state, action) { switch (action.type) { case 'changed_name': return { ...state, name: action.nextName }; case 'incremented_age': return { ...state, age: state.age + 1 }; default: return state; } }
-
Task Management
function tasksReducer(tasks, action) { switch (action.type) { case 'added': return [...tasks, { id: action.id, text: action.text }]; case 'deleted': return tasks.filter(task => task.id !== action.id); default: throw new Error('Unknown action'); } }
Key Features
- State Immutability: Always return new objects for state updates.
- Best for Complex State: Ideal when state transitions are dictated by multiple actions.
- Reducer Function is Pure: The reducer should compute the next state without side effects.
useContext
useContext simplifies accessing and subscribing to context values.
Syntax
const value = useContext(SomeContext);
Usage
Avoids "prop drilling" by sharing values directly.
Examples
- Theme Context
const ThemeContext = React.createContext('light'); function Button() { const theme = useContext(ThemeContext); return <button className={`btn-${theme}`}>Click Me</button>; }
- Combining Context with State
function MyApp() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={theme}> <ChildComponent /> </ThemeContext.Provider> ); }
Caveats
- Ensure components are wrapped in the correct Provider.
useRef
The useRef hook manages mutable references or directly interacts with the DOM.
Syntax
const ref = useRef(initialValue);
Key Concepts
- Mutability: Changes to ref.current don't trigger re-renders.
Common Use Cases
- Referencing DOM Elements
const inputRef = useRef(null); function focusInput() { inputRef.current.focus(); }
- Managing Timers
const intervalRef = useRef(null);
useEffect
The useEffect hook synchronizes components with external systems.
Syntax
useEffect(setup, dependencies);
How It Works
- Setup: Executed after the component renders.
- Cleanup: Returned cleanup logic.
Common Use Cases
- Global Event Listeners
useEffect(() => { const handleResize = () => console.log(window.innerWidth); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []);
- API Calls
useEffect(() => { fetchData(); }, []);
useMemo
useMemo optimizes performance by caching calculations.
Syntax
const memoizedValue = useMemo(calculateValue, dependencies);
Examples
- Filtering Todos
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
useCallback
useCallback optimizes function reuse between renders.
Syntax
const cachedFn = useCallback(fn, dependencies);
Key Use Cases
- Skipping Component Re-Renders
const handleSubmit = useCallback((orderDetails) => { post('/buy', { orderDetails }); }, []);
Custom Hooks
Custom hooks encapsulate reusable logic.
Example: Tracking Online/Offline Status
useOnlineStatus Hook:
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
window.addEventListener('online', handleOnline);
return () => window.removeEventListener('online', handleOnline);
}, []);
return isOnline;
}
Simplified Components:
function StatusBar() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}