← Back to all posts

Introduction to React Hooks

2/20/2025

#react#javascript#frontend
# Introduction to React Hooks: Revolutionizing React Components React Hooks have revolutionized how we write React components. This post introduces the most commonly used hooks like useState, useEffect, and useContext, with practical examples. ## What Are React Hooks? Introduced in React 16.8, hooks are functions that let you "hook into" React state and lifecycle features from function components. Before hooks, you needed to write a class component if you wanted to manage state or use lifecycle methods. Now, you can use all of React's features without classes. Hooks solve several problems that existed in React's class-based component model: - **Reusing stateful logic** between components was difficult - **Complex components** became hard to understand - **Classes** can be confusing for both humans and machines ## Core Hooks Let's explore the most commonly used hooks in React. ### useState The `useState` hook lets you add state to functional components. ```jsx import React, { useState } from 'react'; function Counter() { // Declare a state variable "count" with initial value 0 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ``` **Key points about useState:** - Returns a pair: the current state value and a function to update it - You can call useState multiple times for multiple state variables - The update function can take a value or a function (for updates based on previous state) - State updates trigger re-renders **Using functional updates:** ```jsx // Instead of: setCount(count + 1); // You can use: setCount(prevCount => prevCount + 1); ``` This is especially important when updating state based on its previous value in event handlers or effects. ### useEffect The `useEffect` hook lets you perform side effects in function components. It serves the same purpose as `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount` in React classes. ```jsx import React, { useState, useEffect } from 'react'; function DocumentTitleUpdater() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; // Optional cleanup function (similar to componentWillUnmount) return () => { document.title = 'React App'; }; }, [count]); // Only re-run if count changes return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ``` **Key points about useEffect:** - Runs after every render by default - The second argument (dependency array) controls when the effect runs: - `[]`: Run once after the initial render (like componentDidMount) - `[value1, value2]`: Run when any dependency changes - Omitted: Run after every render - Return a cleanup function to prevent memory leaks **Common use cases for useEffect:** - Data fetching - Setting up subscriptions - Manually changing the DOM - Logging ### useContext The `useContext` hook provides a way to pass data through the component tree without having to pass props down manually at every level. ```jsx import React, { useContext, createContext } from 'react'; // Create a context with a default value const ThemeContext = createContext('light'); function App() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar() { // No need to pass theme through props return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { // Use the context value const theme = useContext(ThemeContext); return ( <button style={{ background: theme === 'dark' ? '#333' : '#FFF', color: theme === 'dark' ? '#FFF' : '#333' }}> I am styled based on the theme context! </button> ); } ``` **Key points about useContext:** - Accepts a context object (created with `createContext`) - Returns the current context value - Always re-renders when the context value changes - Used with `Provider` components higher in the tree ## Additional Hooks Beyond the core hooks, React provides several additional hooks for specific use cases. ### useReducer `useReducer` is an alternative to `useState` for complex state logic. It's especially useful when the next state depends on the previous state or when you have multiple sub-values. ```jsx import React, { useReducer } from 'react'; // Reducer function function counterReducer(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() { // Initialize state with useReducer const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <div> Count: {state.count} <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <button onClick={() => dispatch({ type: 'increment' })}>+</button> </div> ); } ``` ### useCallback `useCallback` returns a memoized callback that only changes if one of the dependencies has changed. This is useful for optimizing performance by preventing unnecessary renders of child components. ```jsx import React, { useState, useCallback } from 'react'; function ParentComponent() { const [count, setCount] = useState(0); // This function is recreated only when count changes const handleClick = useCallback(() => { console.log(`Button clicked, count: ${count}`); }, [count]); return ( <div> <ChildComponent onClick={handleClick} /> <button onClick={() => setCount(count + 1)}> Increment count </button> </div> ); } // This component will only re-render when props actually change const ChildComponent = React.memo(({ onClick }) => { console.log('ChildComponent rendered'); return <button onClick={onClick}>Click me</button>; }); ``` ### useMemo `useMemo` returns a memoized value that only recalculates when one of its dependencies changes. This is useful for expensive calculations. ```jsx import React, { useState, useMemo } from 'react'; function ExpensiveCalculation({ list, filter }) { // This calculation will only run when list or filter changes const filteredList = useMemo(() => { console.log('Filtering list...'); return list.filter(item => item.includes(filter)); }, [list, filter]); return ( <div> <h2>Filtered List</h2> <ul> {filteredList.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> ); } ``` ### useRef `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument. The object persists for the full lifetime of the component. ```jsx import React, { useRef, useEffect } from 'react'; function TextInputWithFocusButton() { // Create a ref const inputRef = useRef(null); // Function to focus the input const focusInput = () => { inputRef.current.focus(); }; // Focus input on mount useEffect(() => { inputRef.current.focus(); }, []); return ( <div> <input ref={inputRef} type="text" /> <button onClick={focusInput}>Focus Input</button> </div> ); } ``` **Common uses for useRef:** - Accessing DOM elements - Keeping mutable values without causing re-renders - Storing previous state values ## Custom Hooks One of the most powerful features of hooks is the ability to create your own custom hooks. This allows you to extract component logic into reusable functions. Here's an example of a custom hook for handling form inputs: ```jsx import { useState } from 'react'; // Custom hook for form input handling function useFormInput(initialValue) { const [value, setValue] = useState(initialValue); function handleChange(e) { setValue(e.target.value); } return { value, onChange: handleChange }; } // Using the custom hook function LoginForm() { const username = useFormInput(''); const password = useFormInput(''); function handleSubmit(e) { e.preventDefault(); console.log('Submitting:', username.value, password.value); } return ( <form onSubmit={handleSubmit}> <div> <label>Username:</label> <input type="text" {...username} /> </div> <div> <label>Password:</label> <input type="password" {...password} /> </div> <button type="submit">Login</button> </form> ); } ``` **Guidelines for custom hooks:** - Hook names should start with "use" (e.g., `useFormInput`) - Hooks can call other hooks - Share stateful logic, not state itself - Each call to a hook gets isolated state ## Rules of Hooks To ensure hooks work correctly, you must follow two rules: 1. **Only call hooks at the top level** of your React function components or custom hooks. Don't call hooks inside loops, conditions, or nested functions. 2. **Only call hooks from React function components or custom hooks**. Don't call hooks from regular JavaScript functions. React provides an ESLint plugin called `eslint-plugin-react-hooks` that enforces these rules. ## Migrating from Classes to Hooks If you're transitioning from class components to function components with hooks, here's a quick reference: | Class Component | Hooks Equivalent | |-----------------|------------------| | `constructor` | `useState` | | `componentDidMount` | `useEffect(() => {}, [])` | | `componentDidUpdate` | `useEffect(() => {})` | | `componentWillUnmount` | `useEffect(() => { return () => {} }, [])` | | `shouldComponentUpdate` | `React.memo` | | `static contextType` | `useContext` | ## Conclusion React Hooks have transformed how we write React components, making code more concise, easier to understand, and more reusable. By embracing hooks, you can write more functional, declarative React code that's easier to test and maintain. As you become more comfortable with hooks, you'll discover that they not only simplify your components but also encourage better patterns and practices in your React applications. Remember to follow the rules of hooks, and don't be afraid to create custom hooks to encapsulate and reuse logic across your components. Happy coding!