Hooks are functions that let you “hook into” React state and lifecycle features from functional components. They were introduced in React 16.8 to enable state and other React features without writing a class.
#1 - useState
useState returns an array with two elements: the current state value and a function to update it. The update function triggers a re-render.
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>
);
}
#2 - useEffect
useEffect lets you perform side effects (data fetching, subscriptions, manual DOM changes) in function components. The second argument is an array of dependencies—the effect runs only when those values change. An empty array [] means the effect runs only once (on mount).
import React, { useState, useEffect } from 'react';
function Example() {
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 (componentWillUnmount)
return () => {
console.log('Cleanup on unmount or before next effect');
};
}, [count]); // Only re-run if count changes
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}*
useEffect lets you perform side effects (data fetching, subscriptions, manual DOM changes) in function components. The second argument is an array of dependencies—the effect runs only when those values change. An empty array [] means the effect runs only once (on mount).
#3 - useContext
useContext accepts a context object and returns the current context value. It eliminates the need for a <Context.Consumer> wrapper.
import React, { useContext } from 'react';
// Create a context with a type
type Theme = 'light' | 'dark';
const ThemeContext = React.createContext<Theme>('dark');
function ThemedButton() {
// Consume the context value
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme === 'dark' ? '#333' : '#FFF' }}>
I am styled by theme context!
</button>
);
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}
#4 - useReduce
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
import React, { useReducer } from 'react';
// Reducer function
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>
</>
);
}
#5 - useMemo
useMemo returns a memoized value. It only recomputes when its dependencies change, helping to avoid expensive calculations on every render.
import React, { useMemo, useState } from 'react';
function ExpensiveComputation({ a, b }) {
const [otherState, setOtherState] = useState(0);
// Memoize the result of an expensive calculation
const result = useMemo(() => {
console.log('Computing...');
return a * b + otherState; // only recomputed when a, b, or otherState change
}, [a, b, otherState]);
return <div>Result: {result}</div>;
}
#6 - useCallback
useCallback returns a memoized version of the callback function. It’s useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.
import React, { useState, useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Memoized callback function
const handleClick = useCallback(() => {
console.log('Button clicked', count);
}, [count]); // only recreated when count changes
return <Child onClick={handleClick} />;
}
function Child({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
#7 - useRef
useRef returns a mutable ref object whose .current property is initialized to the passed argument. It persists across renders and can hold any value, most commonly a reference to a DOM element.
import React, { useRef, useEffect } from 'react';
function TextInputWithFocus() {
const inputRef = useRef(null);
useEffect(() => {
// Focus the input element on mount
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
#8 - custom hooks
You can create your own hooks to reuse stateful logic between components.
import { useState, useEffect } from 'react';
// Custom hook: useWindowWidth
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
// Usage in a component
function MyComponent() {
const width = useWindowWidth();
return <div>Window width: {width}px</div>;
}
Custom hooks let you extract component logic into reusable functions. Their names should start with use to follow the Rules of Hooks.
Rules of Hooks
-
Only call hooks at the top level (not inside loops, conditions, or nested functions).
-
Only call hooks from React function components or custom hooks.
Following these rules ensures that state is preserved correctly between renders.