React Hooks revolutionized the way we write React components, allowing developers to use state and other React features in functional components. Hooks provide a more direct API to the React concepts we know and love—state, context, refs, and more. In this post, we'll explore every built-in React hook and how to use them effectively in your React projects.
Core Hooks
useState
useState
is the most commonly used hook. It allows you to add state to functional 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>
);
}
Here, useState
initializes the state with 0
. setCount
is a function that updates the state.
useEffect
useEffect
allows you to perform side effects in functional components. It runs after every render by default.
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => clearInterval(timer); // Cleanup on unmount
}, []); // Dependency array
return <h1>{count}</h1>;
}
In this example, useEffect
sets up a timer and cleans it up when the component unmounts.
useContext
useContext
is used to access the value of a React context. It's a simpler way to consume context values without using the <Context.Consumer>
component.
import React, { useContext } from 'react';
const UserContext = React.createContext();
function UserProfile() {
const user = useContext(UserContext);
return <div>Hello, {user.name}!</div>;
}
useContext
takes a context object and returns the current context value.
useReducer
useReducer
is useful for managing complex state logic that involves multiple sub-values or when the next state depends on the previous state.
import React, { useReducer } from 'react';
const initialState = { count: 0 };
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, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
useReducer
takes a reducer function and an initial state and returns the current state and a dispatch function.
useRef
useRef
provides a way to persist values between renders without causing a re-render when updated. It is also commonly used to access DOM elements.
import React, { useRef } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</>
);
}
useRef
creates a reference to a DOM element or a mutable value that persists for the full lifetime of the component.
Additional Hooks
useMemo
useMemo
memoizes the result of a calculation and recomputes it only when one of its dependencies has changed. It's useful for performance optimization.
import React, { useMemo, useState } from 'react';
function ExpensiveCalculationComponent({ num }) {
const [multiplier, setMultiplier] = useState(1);
const result = useMemo(() => {
console.log('Calculating...');
return num * multiplier;
}, [num, multiplier]);
return (
<div>
<h1>{result}</h1>
<button onClick={() => setMultiplier(multiplier + 1)}>Increase Multiplier</button>
</div>
);
}
useCallback
useCallback
is similar to useMemo
but is used to memoize functions. It returns a memoized version of a callback function that only changes if one of its dependencies has changed.
import React, { useState, useCallback } from 'react';
function Button({ onClick }) {
console.log('Button re-rendered');
return <button onClick={onClick}>Click me</button>;
}
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<Button onClick={handleClick} />
<p>Count: {count}</p>
</div>
);
}
useLayoutEffect
useLayoutEffect
is similar to useEffect
but fires synchronously after all DOM mutations. It’s useful when you need to read layout from the DOM and re-render synchronously.
import React, { useLayoutEffect, useRef } from 'react';
function LayoutEffectExample() {
const divRef = useRef();
useLayoutEffect(() => {
console.log('Height:', divRef.current.clientHeight);
});
return <div ref={divRef}>Check the console for height</div>;
}
useImperativeHandle
useImperativeHandle
customizes the instance value that is exposed when using ref
. It's often used with forwardRef
.
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
});
function ParentComponent() {
const inputRef = useRef();
return (
<>
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</>
);
}
useDebugValue
useDebugValue
can be used to display a label for custom hooks in React DevTools.
import React, { useState, useDebugValue } from 'react';
// Custom hook to manage user online status
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useDebugValue(isOnline ? 'Online' : 'Offline');
React.useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
// Component using the custom hook
function StatusIndicator() {
const isOnline = useOnlineStatus();
return <span>{isOnline ? '🟢 Online' : '🔴 Offline'}</span>;
}
export default StatusIndicator;
Custom Hooks
Creating custom hooks allows you to extract and reuse component logic. Here’s a basic example of a custom hook for data fetching:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const data = await response.json();
setData(data);
setLoading(false);
}
fetchData();
}, [url]);
return { data, loading };
}
Conclusion
React Hooks provide a powerful and flexible way to build modern React applications using functional components. Mastering these hooks can help you write cleaner, more maintainable code. Start using these hooks today to enhance your React development experience!