React Hooks Explained
React Hooks were introduced in React 16.8 as a way to use state and other React features without writing a class component. They allow you to "hook into" React state and lifecycle features from function components.
Why Hooks?
Before Hooks, if you needed state in a component, you had to use a class component. This led to complex components that were difficult to understand and reuse. Hooks solve these problems by:
- Allowing you to reuse stateful logic between components
- Organizing related code together (instead of splitting across lifecycle methods)
- Using functions instead of classes (which are easier to understand)
The Basic Hooks
useState
The useState
hook lets you add React state to function components:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect
The useEffect
hook lets you perform side effects in function components. It's like componentDidMount
, componentDidUpdate
, and componentWillUnmount
combined:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
// Cleanup function (like 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>
);
}
useContext
The useContext
hook lets you subscribe to React context without introducing nesting:
import React, { useContext } from 'react';
const ThemeContext = React.createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={`btn-${theme}`}>I am styled by theme context!</button>;
}
Additional Hooks
useReducer
useReducer
is usually preferable to useState
when you have complex state logic:
import React, { useReducer } from 'react';
function reducer(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() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useCallback
useCallback
returns a memoized callback that only changes if one of its dependencies changes:
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// This function only changes when count changes
const incrementCount = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<ChildComponent onIncrement={incrementCount} />
<p>Count: {count}</p>
</div>
);
}
useMemo
useMemo
is similar to useCallback
but for values instead of functions:
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ a, b }) {
// Only recalculate when a or b changes
const result = useMemo(() => {
console.log('Calculating...');
return a * b;
}, [a, b]);
return <div>Result: {result}</div>;
}
Building Custom Hooks
One of the most powerful features of Hooks is the ability to create your own custom hooks:
import { useState, useEffect } from 'react';
// Custom hook to manage window size
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
handleResize(); // Set size initially
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures effect runs only once
return windowSize;
}
// Usage in a component
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
<div>
Window size: {width} x {height}
</div>
);
}
Rules of Hooks
There are two essential rules to follow when using Hooks:
- Only call Hooks at the top level - Don't call Hooks inside loops, conditions, or nested functions.
- Only call Hooks from React function components - Don't call Hooks from regular JavaScript functions.
These rules ensure that Hooks are called in the same order each time a component renders, which is important for React to correctly preserve the state of Hooks between multiple useState
and useEffect
calls.
Conclusion
React Hooks provide a more direct API to React concepts you already know: props, state, context, refs, and lifecycle. They allow you to split one component into smaller functions based on what pieces are related, rather than forcing a split based on lifecycle methods.
By using Hooks, you can write more concise and reusable code, making your React applications easier to understand and maintain.