Introduction to React Hooks
2/20/2025
#react#javascript#frontend
# Introduction to React Hooks: Revolutionizing React Components
React Hooks have revolutionized how we write React components. This post introduces the most commonly used hooks like useState, useEffect, and useContext, with practical examples.
## What Are React Hooks?
Introduced in React 16.8, hooks are functions that let you "hook into" React state and lifecycle features from function components. Before hooks, you needed to write a class component if you wanted to manage state or use lifecycle methods. Now, you can use all of React's features without classes.
Hooks solve several problems that existed in React's class-based component model:
- **Reusing stateful logic** between components was difficult
- **Complex components** became hard to understand
- **Classes** can be confusing for both humans and machines
## Core Hooks
Let's explore the most commonly used hooks in React.
### useState
The `useState` hook lets you add state to functional components.
```jsx
import React, { useState } from 'react';
function Counter() {
// Declare a state variable "count" with initial value 0
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
```
**Key points about useState:**
- Returns a pair: the current state value and a function to update it
- You can call useState multiple times for multiple state variables
- The update function can take a value or a function (for updates based on previous state)
- State updates trigger re-renders
**Using functional updates:**
```jsx
// Instead of:
setCount(count + 1);
// You can use:
setCount(prevCount => prevCount + 1);
```
This is especially important when updating state based on its previous value in event handlers or effects.
### useEffect
The `useEffect` hook lets you perform side effects in function components. It serves the same purpose as `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount` in React classes.
```jsx
import React, { useState, useEffect } from 'react';
function DocumentTitleUpdater() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
// Optional cleanup function (similar to componentWillUnmount)
return () => {
document.title = 'React App';
};
}, [count]); // Only re-run if count changes
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
```
**Key points about useEffect:**
- Runs after every render by default
- The second argument (dependency array) controls when the effect runs:
- `[]`: Run once after the initial render (like componentDidMount)
- `[value1, value2]`: Run when any dependency changes
- Omitted: Run after every render
- Return a cleanup function to prevent memory leaks
**Common use cases for useEffect:**
- Data fetching
- Setting up subscriptions
- Manually changing the DOM
- Logging
### useContext
The `useContext` hook provides a way to pass data through the component tree without having to pass props down manually at every level.
```jsx
import React, { useContext, createContext } from 'react';
// Create a context with a default value
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
// No need to pass theme through props
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
// Use the context value
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme === 'dark' ? '#333' : '#FFF', color: theme === 'dark' ? '#FFF' : '#333' }}>
I am styled based on the theme context!
</button>
);
}
```
**Key points about useContext:**
- Accepts a context object (created with `createContext`)
- Returns the current context value
- Always re-renders when the context value changes
- Used with `Provider` components higher in the tree
## Additional Hooks
Beyond the core hooks, React provides several additional hooks for specific use cases.
### useReducer
`useReducer` is an alternative to `useState` for complex state logic. It's especially useful when the next state depends on the previous state or when you have multiple sub-values.
```jsx
import React, { useReducer } from 'react';
// Reducer function
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
// Initialize state with useReducer
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
}
```
### useCallback
`useCallback` returns a memoized callback that only changes if one of the dependencies has changed. This is useful for optimizing performance by preventing unnecessary renders of child components.
```jsx
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// This function is recreated only when count changes
const handleClick = useCallback(() => {
console.log(`Button clicked, count: ${count}`);
}, [count]);
return (
<div>
<ChildComponent onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>
Increment count
</button>
</div>
);
}
// This component will only re-render when props actually change
const ChildComponent = React.memo(({ onClick }) => {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Click me</button>;
});
```
### useMemo
`useMemo` returns a memoized value that only recalculates when one of its dependencies changes. This is useful for expensive calculations.
```jsx
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ list, filter }) {
// This calculation will only run when list or filter changes
const filteredList = useMemo(() => {
console.log('Filtering list...');
return list.filter(item => item.includes(filter));
}, [list, filter]);
return (
<div>
<h2>Filtered List</h2>
<ul>
{filteredList.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
```
### useRef
`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument. The object persists for the full lifetime of the component.
```jsx
import React, { useRef, useEffect } from 'react';
function TextInputWithFocusButton() {
// Create a ref
const inputRef = useRef(null);
// Function to focus the input
const focusInput = () => {
inputRef.current.focus();
};
// Focus input on mount
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
```
**Common uses for useRef:**
- Accessing DOM elements
- Keeping mutable values without causing re-renders
- Storing previous state values
## Custom Hooks
One of the most powerful features of hooks is the ability to create your own custom hooks. This allows you to extract component logic into reusable functions.
Here's an example of a custom hook for handling form inputs:
```jsx
import { useState } from 'react';
// Custom hook for form input handling
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
return {
value,
onChange: handleChange
};
}
// Using the custom hook
function LoginForm() {
const username = useFormInput('');
const password = useFormInput('');
function handleSubmit(e) {
e.preventDefault();
console.log('Submitting:', username.value, password.value);
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username:</label>
<input type="text" {...username} />
</div>
<div>
<label>Password:</label>
<input type="password" {...password} />
</div>
<button type="submit">Login</button>
</form>
);
}
```
**Guidelines for custom hooks:**
- Hook names should start with "use" (e.g., `useFormInput`)
- Hooks can call other hooks
- Share stateful logic, not state itself
- Each call to a hook gets isolated state
## Rules of Hooks
To ensure hooks work correctly, you must follow two rules:
1. **Only call hooks at the top level** of your React function components or custom hooks. Don't call hooks inside loops, conditions, or nested functions.
2. **Only call hooks from React function components or custom hooks**. Don't call hooks from regular JavaScript functions.
React provides an ESLint plugin called `eslint-plugin-react-hooks` that enforces these rules.
## Migrating from Classes to Hooks
If you're transitioning from class components to function components with hooks, here's a quick reference:
| Class Component | Hooks Equivalent |
|-----------------|------------------|
| `constructor` | `useState` |
| `componentDidMount` | `useEffect(() => {}, [])` |
| `componentDidUpdate` | `useEffect(() => {})` |
| `componentWillUnmount` | `useEffect(() => { return () => {} }, [])` |
| `shouldComponentUpdate` | `React.memo` |
| `static contextType` | `useContext` |
## Conclusion
React Hooks have transformed how we write React components, making code more concise, easier to understand, and more reusable. By embracing hooks, you can write more functional, declarative React code that's easier to test and maintain.
As you become more comfortable with hooks, you'll discover that they not only simplify your components but also encourage better patterns and practices in your React applications.
Remember to follow the rules of hooks, and don't be afraid to create custom hooks to encapsulate and reuse logic across your components. Happy coding!