React Hooks made using state and other features in functional components possible, completely changing the way we create React components. Hooks were first introduced in React 16.8, and they often remove the requirement for class components, making your code simpler to read and manage. In this guide, we’ll explore all React 18 hooks with detailed explanations, practical code examples
What are React Hooks?
React Hooks allows you to use state and lifecycle methods in functional components. They make your code more reusable and reduce the complexity of state management and side effects.
Why Use React Hooks?
- Simplify state management in functional components.
- Improve code readability.
- Promote reusable and testable logic via custom hooks.
Rules of Hooks
Before diving into individual hooks, remember these rules of hooks:
- Call hooks at the top level: Don’t call hooks inside loops, conditions, or nested functions.
- Call hooks only in React functions: Use hooks in functional components or custom hooks.
Core Hooks
- useState
- useEffect
- useContext
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
Additional Hooks
- useLayoutEffect
- useDebugValue
New React 18 Updates
- Automatic batching for hooks (
useState,useReducer). - Improved Suspense for data fetching with hooks.
1. useState: State Management Simplified
React 18 improves automatic batching, which now applies to multiple useState calls within an event handler or function.
import React, { useState } from "react";
function BatchExample() {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const handleClick = () => {
setCount((prev) => prev + 1);
setName("React 18");
// Updates are batched together for a single render
};
return (
<div>
<h1>Count: {count}</h1>
<h2>Name: {name}</h2>
<button onClick={handleClick}>Update</button>
</div>
);
}
export default BatchExample;
2. useEffect: Managing Side Effects
React 18 optimizes useEffect with automatic batching for event handlers. Effects continue to fire after the render cycle.
import React, { useState, useEffect } from "react";
function EffectExample() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Count updated:", count);
}, [count]);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
</div>
);
}
export default EffectExample;
3. React 18 Hooks for Concurrent Rendering
React 18 introduced concurrent rendering, making applications more responsive by breaking rendering into multiple steps. Hooks like useDeferredValue and useTransition allow developers to leverage these features.
4. useTransition: Prioritize UI Updates
The useTransition hook delays less urgent updates, improving responsiveness for high-priority UI tasks.
import React, { useState, useTransition } from "react";
function TransitionExample() {
const [input, setInput] = useState("");
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setInput(e.target.value);
startTransition(() => {
setList(Array(20000).fill(e.target.value));
});
};
return (
<div>
<input type="text" value={input} onChange={handleChange} />
{isPending && <p>Updating...</p>}
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default TransitionExample;
5. useDeferredValue: Avoid Lag in Complex Updates
The useDeferredValue hook defers updates to less urgent UI elements, ensuring smooth interactions.
import React, { useState, useDeferredValue } from "react";
function DeferredExample() {
const [input, setInput] = useState("");
const deferredValue = useDeferredValue(input);
return (
<div>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
<p>Deferred Value: {deferredValue}</p>
</div>
);
}
export default DeferredExample;
6. useId: Generate Unique IDs for Accessibility
The useId hook generates unique IDs to improve accessibility and prevent collisions.
import React, { useId } from "react";
function Form() {
const id = useId();
return (
<form>
<label htmlFor={`${id}-username`}>Username</label>
<input id={`${id}-username`} type="text" />
<label htmlFor={`${id}-password`}>Password</label>
<input id={`${id}-password`} type="password" />
</form>
);
}
export default Form;
7. Improved Suspense with React 18 Hooks
React 18 fully supports data fetching with Suspense, enabling smoother asynchronous UI rendering.
import React, { Suspense } from "react";
const UserProfile = React.lazy(() => import("./UserProfile"));
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<UserProfile />
</Suspense>
);
}
export default App;
8. useContext: Access Global State
The useContext hook allows you to consume values from React’s Context API.
import React, { useContext, createContext } from "react";
const ThemeContext = createContext("light");
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <p>Current Theme: {theme}</p>;
}
export default App;
9. useReducer: Advanced State Management
Use useReducer for complex state logic, similar to Redux-like reducers.
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 (
<div>
<h1>{state.count}</h1>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}
export default Counter;
10. useCallback: Optimize Functions
The useCallback hook memoizes a function, preventing it from being re-created unnecessarily.
import React, { useCallback, useState } from "react";
function Parent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((c) => c + 1);
}, []);
return (
<div>
<Child onClick={increment} />
<h1>{count}</h1>
</div>
);
}
function Child({ onClick }) {
console.log("Child rendered");
return <button onClick={onClick}>Increment</button>;
}
export default Parent;
11. useMemo: Optimize Calculations
The useMemo hook memoizes expensive calculations to improve performance.
import React, { useMemo, useState } from "react";
function ExpensiveCalculation({ num }) {
const compute = (n) => {
console.log("Calculating...");
return n * 2;
};
const result = useMemo(() => compute(num), [num]);
return <h1>Result: {result}</h1>;
}
export default ExpensiveCalculation;
12. useRef: Access DOM Elements
The useRef hook provides a way to reference DOM nodes or persist values across renders.
import React, { useRef } from "react";
function TextInput() {
const inputRef = useRef();
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
export default TextInput;
13.useImperativeHandle: Customize Ref Behavior
This hook customizes the ref behaviour for a child component.
import React, { useImperativeHandle, useRef, forwardRef } from "react";
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
}));
return <input ref={inputRef} type="text" />;
});
function Parent() {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</div>
);
}
export default Parent;
14. Custom Hooks
Custom hooks allow you to encapsulate reusable logic.
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then(setData);
}, [url]);
return data;
}
React hooks simplify the way you manage state, side effects, and reusable logic. By mastering hooks, you can write cleaner and more maintainable React applications. Share your favourite hooks in the comments below!


