React Hooks are a powerful feature introduced in React 16.8 that allow you to use state and other React features in functional components without writing class components. They make your code cleaner, more reusable, and easier to understand. In this blog, we’ll go through all the built-in React Hooks, explain them in simple words, provide easy code examples, and also dive into creating custom hooks.
1. useState - Manage State in Functional Components
The useState
hook lets you add state to your functional components. It gives you a state variable and a function to update it.
Syntax
const [state, setState] = useState(initialValue);
Example
Let’s create a simple counter app.
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0); // Initial state is 0
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Counter;
Explanation
count
is the state variable.setCount
is the function to update the state.- When you click the button,
setCount
updates thecount
, and the component re-renders with the new value.
2. useEffect - Handle Side Effects
The useEffect
hook lets you perform side effects in your components, like fetching data, updating the DOM, or setting up subscriptions. It’s like the combination of componentDidMount
, componentDidUpdate
, and componentWillUnmount
in class components.
Syntax
useEffect(() => {
// Side effect code here
return () => {
// Cleanup code (optional)
};
}, [dependencies]);
Example
Let’s fetch some data when the component mounts.
import React, { useState, useEffect } from "react";
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => response.json())
.then((data) => setData(data));
}, []); // Empty array means this runs only once when the component mounts
return <div>{data ? <p>{data.title}</p> : <p>Loading...</p>}</div>;
}
export default DataFetcher;
Explanation
useEffect
runs the fetch request when the component mounts (because of the empty[]
dependency array).- The data is stored in the
data
state usingsetData
. - If the dependency array had a value like
[count]
, the effect would run every timecount
changes.
3. useContext - Access Context Easily
The useContext
hook lets you access the React Context API without wrapping your component in a Consumer
.
Syntax
const value = useContext(MyContext);
Example
Let’s create a simple theme switcher using context.
import React, { useContext, createContext } from "react";
// Create a context
const ThemeContext = createContext();
function ThemeComponent() {
const theme = useContext(ThemeContext); // Access the context value
return <p>The current theme is {theme}</p>;
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemeComponent />
</ThemeContext.Provider>
);
}
export default App;
Explanation
- We create a
ThemeContext
usingcreateContext()
. - The
App
component provides the theme value ("dark"
) usingThemeContext.Provider
. - The
ThemeComponent
usesuseContext
to access the theme value and display it.
4. useReducer - Manage Complex State Logic
The useReducer
hook is an alternative to useState
when you have complex state logic or multiple state transitions.
Syntax
const [state, dispatch] = useReducer(reducer, initialState);
Example
Let’s create a counter with increment, decrement, and reset actions.
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 };
case "reset":
return { count: 0 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
<button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
}
export default Counter;
Explanation
reducer
is a function that defines how the state changes based on the action.dispatch
is used to send actions to the reducer.- This is useful for managing more complex state logic compared to
useState
.
5. useCallback - Memoize Functions
The useCallback
hook returns a memoized version of a callback function that only changes if one of its dependencies changes. This is useful for optimizing performance when passing callbacks to child components.
Syntax
const memoizedCallback = useCallback(() => {
// Your callback logic
}, [dependencies]);
Example
import React, { useState, useCallback } from "react";
function Parent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<Child onIncrement={increment} />
</div>
);
}
function Child({ onIncrement }) {
return <button onClick={onIncrement}>Increment</button>;
}
export default Parent;
Explanation
useCallback
ensures theincrement
function doesn’t get recreated on every render unlesscount
changes.- This prevents unnecessary re-renders of the
Child
component.
6. useMemo - Memoize Expensive Computations
The useMemo
hook memoizes the result of a computation so it’s only recalculated when its dependencies change. This is great for optimizing performance.
Syntax
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Example
import React, { useState, useMemo } from "react";
function ExpensiveCalculation() {
const [count, setCount] = useState(0);
const expensiveValue = useMemo(() => {
console.log("Calculating...");
return count * 2;
}, [count]);
return (
<div>
<p>Expensive Value: {expensiveValue}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default ExpensiveCalculation;
Explanation
- The
expensiveValue
is only recalculated whencount
changes. - Without
useMemo
, the computation would run on every render, even ifcount
doesn’t change.
7. useRef - Reference DOM Elements or Persist Values
The useRef
hook lets you create a mutable reference that persists across renders. It’s often used to access DOM elements or store values that don’t trigger re-renders when updated.
Syntax
const ref = useRef(initialValue);
Example
Let’s create an input field and focus it on button click.
import React, { useRef } from "react";
function FocusInput() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
export default FocusInput;
Explanation
inputRef
holds a reference to the input element.inputRef.current
gives you direct access to the DOM element, so you can call methods likefocus()
.
8. useLayoutEffect - Like useEffect, but Runs Synchronously
The useLayoutEffect
hook is similar to useEffect
, but it runs synchronously after all DOM mutations. Use it when you need to measure or update the DOM before the browser paints.
Syntax
useLayoutEffect(() => {
// Your code
}, [dependencies]);
Example
import React, { useLayoutEffect, useRef } from "react";
function LayoutEffectExample() {
const divRef = useRef(null);
useLayoutEffect(() => {
console.log("Div width:", divRef.current.offsetWidth);
}, []);
return <div ref={divRef} style={{ width: "200px", height: "200px", background: "lightblue" }} />;
}
export default LayoutEffectExample;
Explanation
useLayoutEffect
runs after the DOM updates but before the browser paints, so it’s ideal for measuring DOM elements.- Use
useEffect
for most side effects unless you specifically need synchronous updates.
9. useDebugValue - Debug Custom Hooks
The useDebugValue
hook is used to display a label for custom hooks in React DevTools, making debugging easier.
Syntax
useDebugValue(value);
Explanation
This is typically used inside custom hooks. We’ll see it in action when we create a custom hook below.
What Are Custom Hooks?
Custom Hooks are JavaScript functions that start with use
and allow you to reuse stateful logic across multiple components. They let you extract logic from components and share it without repeating code.
Creating a Custom Hook
Let’s create a custom hook to manage form input state.
Example: useFormInput
import { useState } from "react";
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
const handleChange = (e) => {
setValue(e.target.value);
};
return {
value,
onChange: handleChange,
};
}
function Form() {
const nameInput = useFormInput(""); // Custom hook for name
const emailInput = useFormInput(""); // Custom hook for email
return (
<div>
<input placeholder="Name" {...nameInput} />
<input placeholder="Email" {...emailInput} />
<p>Name: {nameInput.value}</p>
<p>Email: {emailInput.value}</p>
</div>
);
}
export default Form;
Explanation
useFormInput
is a custom hook that manages the state of an input field.- It returns the current
value
and anonChange
handler. - We use it twice in the
Form
component to manage two different inputs (name and email). - This avoids duplicating the same state logic in multiple places.
Adding useDebugValue
to a Custom Hook
Let’s add useDebugValue
to our custom hook for better debugging.
import { useState, useDebugValue } from "react";
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
const handleChange = (e) => {
setValue(e.target.value);
};
// Add debug value for React DevTools
useDebugValue(value ? `Input: ${value}` : "Empty");
return {
value,
onChange: handleChange,
};
}
export default useFormInput;
Explanation
useDebugValue
adds a label to the custom hook in React DevTools.- When you inspect the
useFormInput
hook in DevTools, it will show the current input value (or "Empty" if the input is empty).
Conclusion
React Hooks make functional components more powerful and reusable. Here’s a quick recap of what we covered:
- useState: Manage state in functional components.
- useEffect: Handle side effects like fetching data.
- useContext: Access context values easily.
- useReducer: Manage complex state logic.
- useCallback: Memoize functions for performance.
- useMemo: Memoize expensive computations.
- useRef: Reference DOM elements or persist values.
- useLayoutEffect: Like
useEffect
, but synchronous. - useDebugValue: Debug custom hooks in DevTools.
We also learned how to create custom hooks to reuse logic across components, making our code DRY (Don’t Repeat Yourself).
Start using Hooks in your projects—they’ll make your React code cleaner and more maintainable! If you have any questions or want more examples, let me know in the comments. Happy coding! 🚀