🚀 React Hooks Explained — What They Are
🟦 What Are React Hooks?
React Hooks are special functions used inside functional components that let you manage state, lifecycle, and other powerful features — without using class components.
🔹 Before Hooks → only class components had state & lifecycle
🔹 After Hooks → functional components became powerful
1️⃣ useState — Manage Component State
useState() helps you create a state variable and update it inside a component.
👉 Example:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0); // state variable
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
🔹 count → current value 🔹 setCount → function to update value 🔹 Component re-renders whenever state changes
2️⃣ useEffect — Handle Side Effects
useEffect() runs side effects after the component renders.
✨ Side effects include: • API calls • Timers • Event listeners • console logs
👉 Example:
import { useEffect, useState } from "react";
function WelcomeMessage() {
const [message, setMessage] = useState("Loading...");
useEffect(() => {
console.log("Component mounted!");
setTimeout(() => {
setMessage("Welcome to React!");
}, 1000);
}, []); // runs only once when component loads
return <h1>{message}</h1>;
}
3️⃣ useRef — Hold Value Without Re-render + Access DOM
useRef() is used to store a value that does NOT cause re-render when updated. It is also commonly used to access DOM elements directly.
✨ Why use useRef?
✔ Store values between renders ✔ Update data without re-rendering ✔ Access input, button, or any DOM element ✔ Good for timers, counters, scroll tracking, focus, etc.
✅ Example 1: Update value without re-render
import { useRef, useState } from "react";
function ClickTracker() {
const clickCountRef = useRef(0); // does NOT re-render component
const [uiCount, setUiCount] = useState(0); // triggers re-render
const handleClick = () => {
clickCountRef.current += 1; // updates silently
setUiCount(uiCount + 1); // updates UI
console.log("Ref Count:", clickCountRef.current);
};
return (
<div>
<h3>UI Count (re-renders): {uiCount}</h3>
<h3>Ref Count (no re-render): {clickCountRef.current}</h3>
<button onClick={handleClick}>Click Me</button>
</div>
);
}
📌 clickCountRef.current changes but the component will not re-render. 📌 uiCount changes → React re-renders.
✅ Example 2: Access DOM directly (focus input)
import { useRef } from "react";
function FocusInput() {
const inputRef = useRef(null);
const focusField = () => {
inputRef.current.focus(); // Access DOM element
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Type here..." />
<button onClick={focusField}>Focus Input</button>
</div>
);
}
📌 inputRef.current gives you the DOM input element. 📌 Clicking the button focuses the input automatically.
4️⃣ useMemo — Improve Performance by Memoizing Values
useMemo() calculates a value only when its dependency changes, not on every render.
✔ Prevents expensive calculations from re-running ✔ Returns value (not a function) ✔ Good for filtering, sorting, heavy loops, derived data
import { useMemo, useState } from "react";
function PriceCalculator({ price }) {
const [count, setCount] = useState(0);
const total = useMemo(() => {
console.log("Calculating total...");
return price * 10; // expensive calculation simulated
}, [price]); // runs only when price updates
return (
<div>
<h3>Total Price: {total}</h3>
<button onClick={() => setCount(count + 1)}>
Re-render: {count}
</button>
</div>
);
}
📌 useMemo prevents unnecessary recalculation 📌 useEffect runs code but does not return a computed value 📌 useMemo runs the function and returns the output
5️⃣ useCallback — Memoize Functions
useCallback() returns a memoized version of a function, so the function is recreated only when dependencies change.
✔ Good when passing functions to child components ✔ Avoids unnecessary re-renders ✔ Keeps function reference stable
Recommended by LinkedIn
import { useCallback, useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((c) => c + 1);
}, []); // same function reference every render
return (
<button onClick={increment}>Count: {count}</button>
);
}
📌 useMemo → memoizes values 📌 useCallback → memoizes functions
6️⃣ useContext — Share Data Without Props Drilling
useContext() allows you to access global data without passing props manually at every level.
✔ Good for user info, theme, language, auth data
import { createContext, useContext } from "react";
const UserContext = createContext();
function App() {
return (
<UserContext.Provider value="Ragu">
<Profile />
</UserContext.Provider>
);
}
function Profile() {
const user = useContext(UserContext);
return <h3>Hello, {user}</h3>;
}
📌 No need to pass props through multiple components 📌 Clean and easier global state sharing
7️⃣ useReducer — Handle Complex State
useReducer() is used when you have multiple action types or complex state updates.
✔ Better than useState for multi-step logic ✔ Works like Redux reducer
“useState → simple state useReducer → complex state updates”
import { 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:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<h3>Count: {state.count}</h3>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
📌 Best for structured, predictable state updates
8️⃣ useLayoutEffect — Runs Before Screen Paints
useLayoutEffect() works similar to useEffect, but timing is different.
🔥 Super Simple Difference
Hook When does it run?
useEffect - After the UI is painted (async)
useLayoutEffect - Before the UI is painted (sync)
Used for: ✔ DOM measurements ✔ Layout adjustments ✔ Avoid flickering
import { useLayoutEffect, useRef } from "react";
function Box() {
const boxRef = useRef(null);
useLayoutEffect(() => {
console.log("Width:", boxRef.current.offsetWidth);
}, []);
return <div ref={boxRef} style={{ width: "200px" }}>Box</div>;
}
📌 useLayoutEffect runs earlier → guarantees DOM is measured before painting
9️⃣ Custom Hooks — Reuse Your Logic Across Components
A custom hook is simply a JavaScript function that uses one or more React Hooks (useState, useEffect, etc.) and allows you to extract reusable logic from components.
✅ Why use Custom Hooks?
✔ Reuse logic across multiple components ✔ Keep components clean and small ✔ Avoid repeating useEffect / useState code ✔ Share API calls, form logic, timers, storage, etc.
⭐ Example: Custom Hook for Local Storage
This custom hook saves data to localStorage automatically.
import { useState, useEffect } from "react";
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(
localStorage.getItem(key) || initialValue
);
useEffect(() => {
localStorage.setItem(key, value);
}, [value, key]);
return [value, setValue];
}
export default useLocalStorage;
📌 How to use it in a component?
import useLocalStorage from "./useLocalStorage";
function Profile() {
const [name, setName] = useLocalStorage("username", "Guest");
return (
<>
<h3>Hello, {name}</h3>
<button onClick={() => setName("Ragu")}>Set Name</button>
</>
);
}
✔ Value is automatically saved ✔ No repeating useEffect logic ✔ Cleaner components
I was asked about the hooks in an interview. But I failed to answer that. Thanks for explaining it.