React Hooks Introduction
What Are React Hooks?
React Hooks are functions that let you "hook into" React state and lifecycle features from functional components. They were introduced in React 16.8 and have revolutionized the way we write React applications by enabling the use of state and other React features without writing a class.
Before hooks, if you needed state or lifecycle methods in a component, you had to use a class component. With hooks, you can add state and other React features to functional components, making your code more concise, easier to understand, and easier to test.
Why Were Hooks Introduced?
Hooks were introduced to solve several problems that existed in React:
-
Complex Components Become Hard to Understand: Components often grew from simple to complex over time, with related logic scattered across different lifecycle methods.
-
Classes Can Be Confusing: Classes can be a barrier to learning React, requiring understanding of
this
in JavaScript which behaves differently than in most languages. -
Reusing Stateful Logic: Before hooks, patterns like render props and higher-order components were used to reuse stateful logic, which made code harder to follow.
Let's visualize the difference between class and functional components:
The Basic Hooks
React provides a few built-in hooks. The most common ones are:
- useState: Allows functional components to have state
- useEffect: Handles side effects in your components
- useContext: Helps functional components use React context
Let's explore each of these in more detail with examples.
The useState Hook
The useState
hook allows you to add state to functional components. Here's a simple counter example:
import React, { useState } from 'react';
function Counter() {
// Declare a state variable named "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>
);
}
Let's break down what's happening:
- We import the
useState
hook from React - Inside our functional component, we call
useState
with an initial state value (0) useState
returns an array with two elements: the current state value and a function to update it- We use array destructuring to assign names to these elements:
count
andsetCount
- When the button is clicked,
setCount
is called with the new count value
The useEffect Hook
The useEffect
hook lets you perform side effects in function components. It serves the same purpose as componentDidMount
, componentDidUpdate
, and componentWillUnmount
in class components, but unified into a single API.
Here's an example that updates the document title when the count changes:
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`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
In this example, useEffect
runs after every render, including the first render. It updates the document title to reflect the current count.
You can also control when the effect runs by providing a dependency array:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
With an empty dependency array, the effect runs only once after the initial render:
useEffect(() => {
console.log('Component mounted');
// Optional cleanup function
return () => {
console.log('Component will unmount');
};
}, []); // Empty dependency array means "run once"
Real-World Example: A Todo List with Hooks
Let's create a simple todo list application using hooks:
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim() !== '') {
setTodos([...todos, { text: input, completed: false }]);
setInput(''); // Clear the input field
}
};
const toggleTodo = (index) => {
const newTodos = [...todos];
newTodos[index].completed = !newTodos[index].completed;
setTodos(newTodos);
};
return (
<div>
<h1>Todo List</h1>
<div>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Add a todo"
/>
<button onClick={addTodo}>Add</button>
</div>
<ul>
{todos.map((todo, index) => (
<li
key={index}
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
cursor: 'pointer'
}}
onClick={() => toggleTodo(index)}
>
{todo.text}
</li>
))}
</ul>
<div>
<p>Total todos: {todos.length}</p>
<p>Completed: {todos.filter(todo => todo.completed).length}</p>
</div>
</div>
);
}
In this example:
- We use
useState
to manage the list of todos and the input field - We define functions to add and toggle todos
- We render the list of todos with styling based on their completed status
- We display statistics about the todos
Combining Multiple Hooks
One of the powerful aspects of hooks is how they can be combined. Let's enhance our todo list to save todos to localStorage
:
import React, { useState, useEffect } from 'react';
function PersistentTodoList() {
// Initialize state from localStorage or empty array
const [todos, setTodos] = useState(() => {
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
return JSON.parse(savedTodos);
}
return [];
});
const [input, setInput] = useState('');
// Save to localStorage whenever todos change
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
const addTodo = () => {
if (input.trim() !== '') {
setTodos([...todos, { text: input, completed: false }]);
setInput('');
}
};
const toggleTodo = (index) => {
const newTodos = [...todos];
newTodos[index].completed = !newTodos[index].completed;
setTodos(newTodos);
};
return (
<div>
<h1>Persistent Todo List</h1>
<div>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Add a todo"
/>
<button onClick={addTodo}>Add</button>
</div>
<ul>
{todos.map((todo, index) => (
<li
key={index}
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
cursor: 'pointer'
}}
onClick={() => toggleTodo(index)}
>
{todo.text}
</li>
))}
</ul>
<div>
<p>Total todos: {todos.length}</p>
<p>Completed: {todos.filter(todo => todo.completed).length}</p>
</div>
</div>
);
}
In this enhanced example:
- We initialize our state with todos from
localStorage
if they exist - We use the
useEffect
hook to save todos tolocalStorage
whenever they change - We're using a function in the
useState
initial value to avoid expensive operations on every render
Rules of Hooks
There are two important rules you must follow when using hooks:
-
Only Call Hooks at the Top Level
Don't call hooks inside loops, conditions, or nested functions. Always use hooks at the top level of your React function. -
Only Call Hooks from React Functions
Call hooks from React functional components or custom hooks, not regular JavaScript functions.
Following these rules ensures that hooks work correctly across multiple useState
and useEffect
calls in a component.
// ❌ Don't do this - hook inside a condition
function BadComponent() {
const [count, setCount] = useState(0);
if (count > 0) {
// This breaks the rules of hooks!
const [name, setName] = useState('');
}
// ...rest of component
}
// ✅ Do this instead
function GoodComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// Use the state conditionally, not the hook itself
if (count > 0) {
// do something with name
}
// ...rest of component
}
Summary
React Hooks provide a powerful way to use state and other React features without writing classes. In this introduction, we've covered:
- What hooks are and why they were introduced
- The basic hooks:
useState
anduseEffect
- How to build a real-world application using hooks
- The rules of hooks that you need to follow
Hooks make functional components more powerful and promote code reuse through custom hooks, which we'll explore in future lessons.
Additional Resources and Exercises
Exercises
-
Counter with Increment/Decrement
Extend the counter example to include buttons for both incrementing and decrementing the count. -
Form with Multiple Fields
Create a form with multiple input fields (name, email, message) using hooks to manage the state. -
Timer Application
Build a simple timer that counts seconds and has start, pause, and reset functionality using hooks.
Resources for Further Learning
- React Official Hooks Documentation
- Thinking in React Hooks
- useHooks - A collection of easy-to-understand custom React hooks
In the next section, we'll dive deeper into the useState
hook and explore its advanced features.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)