React Hooks
React introduced hooks in version 16.8 to allow developers to use state and other React features in functional components, which were previously only available in class components. Hooks make it possible to write components that are concise, easy to read, and maintain while providing more powerful capabilities.
useState
– Managing State in Functional Components
The useState
hook allows you to add state to functional components. Before hooks, managing state was only possible in class components. useState
makes it easy to manage component state in functional components.
Syntax:
const [state, setState] = useState(initialState);
state
: The current state value.setState
: A function that updates the state value. It triggers a re-render of the component when the state is updated.initialState
: The initial value for the state.
Example:
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
In the above example:
useState(0)
initializes the state with0
.setCount
is used to update the state when the button is clicked.
Important Notes:
- The state is preserved between re-renders.
- The
setState
function is asynchronous and may batch updates for performance optimization.
useEffect
– Managing Side Effects
The useEffect
hook is used to perform side effects in functional components, such as fetching data, subscribing to external events, or manually modifying the DOM. It can be thought of as a combination of componentDidMount
, componentDidUpdate
, and componentWillUnmount
lifecycle methods in class components.
Syntax:
useEffect(() => {
// Code to run when component mounts or state/props change
}, [dependencies]);
- The callback function inside
useEffect
is run after the component renders. - Dependencies: The second argument is an array of dependencies. The effect runs only if the values in the dependency array change. If the array is empty, the effect runs only once (after the initial render).
Example:
import React, { useState, useEffect } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
// Cleanup function to clear interval when component unmounts or dependencies change
return () => clearInterval(intervalId);
}, []); // Empty dependency array ensures this effect runs only once (on mount)
return <div>Timer: {seconds}s</div>;
}
In this example:
useEffect
starts an interval that increments theseconds
state every second.- The cleanup function clears the interval when the component is unmounted or before running the effect again.
Important Notes:
- Cleanup: If your effect subscribes to external services, uses timers, or sets up side effects, you should return a cleanup function to remove them before the component unmounts or before running the effect again.
- Dependencies: The effect is rerun when any value in the dependency array changes.
useContext
– Managing Global State Across Components
The useContext
hook allows you to access the value of a React context directly in a functional component. It is used to share values like themes, authentication status, or settings across many components without having to pass props down manually at each level.
Syntax:
const value = useContext(MyContext);
MyContext
: A context object created usingReact.createContext()
.value
: The current context value.
Example:
import React, { createContext, useContext, useState } from "react";
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () =>
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemedComponent() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
function App() {
return (
<ThemeProvider>
<ThemedComponent />
</ThemeProvider>
);
}
In the above example:
ThemeContext.Provider
provides thetheme
andtoggleTheme
values to the component tree.useContext(ThemeContext)
is used inThemedComponent
to access thetheme
andtoggleTheme
functions.
Important Notes:
useContext
will re-render the component whenever the context value changes.- Context is often used for global state like user authentication, themes, etc.
useReducer
– Complex State Management
The useReducer
hook is used to manage more complex state logic in functional components. It’s particularly useful when dealing with complex state transitions, such as handling multiple actions or when the state depends on the previous state.
useReducer
is similar to useState
, but instead of just setting state directly, you dispatch actions to a reducer function, which returns a new state.
Syntax:
const [state, dispatch] = useReducer(reducer, initialState);
reducer
: A function that determines how the state changes based on an action.initialState
: The initial state value.dispatch
: A function used to send actions to the reducer to update the state.
Example:
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:
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>
</div>
);
}
In this example:
- The reducer function handles state transitions based on the action type.
- The
dispatch
function is used to send actions ({ type: 'increment' }
or{ type: 'decrement' }
) to the reducer.
Important Notes:
useReducer
is useful when state logic is complex or when there are multiple state updates that depend on each other.- It provides a more predictable and organized way to handle state, especially in larger applications.
useMemo
and useCallback
– Performance Optimization
React provides useMemo
and useCallback
hooks to optimize performance by memoizing values and functions, ensuring that expensive calculations or functions are not re-executed unnecessarily.
useMemo
: Memoize Values
useMemo
returns a memoized value. It recomputes the value only when one of the dependencies has changed. This is useful for optimizing expensive calculations that should not run on every render.
Syntax:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- The callback function runs the expensive calculation.
- The dependency array determines when the memoized value should be recomputed.
Example:
import React, { useMemo, useState } from "react";
function ExpensiveComputation({ a, b }) {
const computedValue = useMemo(() => {
console.log("Computing...");
return a + b;
}, [a, b]);
return <p>Computed Value: {computedValue}</p>;
}
In this example, useMemo
ensures that the computation (a + b
) is only re-run if either a
or b
changes.
useCallback
: Memoize Functions
useCallback
is similar to useMemo
, but instead of memoizing a value, it memoizes a function. It’s particularly useful when passing functions as props to child components to prevent unnecessary re-renders.
Syntax:
const memoizedFunction = useCallback(() => {
/* function code */
}, [dependencies]);
Example:
import React, { useCallback, useState } from "react";
function Button({ onClick }) {
console.log("Button re-rendered");
return <button onClick={onClick}>Click me</button>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<Button onClick={handleClick} />
<p>Count: {count}</p>
</div>
);
}
In this example, useCallback
ensures that the handleClick
function is not recreated on every render unless the count
value changes.
Custom Hooks – Reusable Logic
Custom hooks allow you to extract and reuse logic in different components. Custom hooks
are just JavaScript functions that call React hooks and can be shared across multiple components.
Example:
import { useState, useEffect } from "react";
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error("Error setting local storage", error);
}
};
return [storedValue, setValue];
}
In this example, useLocalStorage
is a custom hook that manages state and synchronizes it with localStorage
. It can be used in any component that needs to store data in localStorage
.
Important Notes:
- Custom hooks follow the same rules as React hooks (e.g., they must start with
use
). - They are great for sharing reusable logic without the need for higher-order components (HOCs) or render props.