What are React hooks?

Free Coding Questions Catalog
Boost your coding skills with our essential coding questions catalog. Take a step towards a better tech career now!

React Hooks

React Hooks are special functions introduced in React 16.8 that allow you to use state and other React features in functional components. Prior to Hooks, only class components could manage state and utilize lifecycle methods. Hooks have revolutionized how developers build React applications by enabling the use of stateful logic and side effects within functional components, promoting cleaner and more reusable code.

1. Why Hooks Were Introduced

  • Simplify Components: Reduce the complexity of class components by enabling state and lifecycle features in functional components.
  • Reuse Stateful Logic: Allow the reuse of stateful logic without changing the component hierarchy, making code more modular and maintainable.
  • Improve Readability: Functional components with Hooks are generally easier to read and test compared to class components.
  • Encourage Best Practices: Promote the use of pure functions and avoid common pitfalls associated with this in class components.

2. Basic Rules of Hooks

To use Hooks effectively and avoid bugs, adhere to the following rules:

  1. Only Call Hooks at the Top Level: Don’t call Hooks inside loops, conditions, or nested functions. Always use Hooks at the top level of your React function to ensure they’re called in the same order each time a component renders.
  2. Only Call Hooks from React Functions: Hooks should be called from React functional components or from custom Hooks, not from regular JavaScript functions.

3. Commonly Used Built-In Hooks

a. useState

  • Purpose: Adds state to functional components.
  • Syntax:
    const [state, setState] = useState(initialState);
  • Example:
    import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }

b. useEffect

  • Purpose: Performs side effects in functional components, such as data fetching, subscriptions, or manually changing the DOM.
  • Syntax:
    useEffect(() => { // Side effect code here return () => { // Cleanup code here (optional) }; }, [dependencies]);
  • Example:
    import React, { useState, useEffect } from 'react'; function DataFetcher() { const [data, setData] = useState(null); useEffect(() => { fetch('https://api.example.com/data') .then(response => response.json()) .then(json => setData(json)) .catch(error => console.error('Error fetching data:', error)); }, []); // Empty dependency array means this runs once after initial render if (!data) { return <p>Loading...</p>; } return ( <div> <h1>Fetched Data:</h1> <pre>{JSON.stringify(data, null, 2)}</pre> </div> ); }

c. useContext

  • Purpose: Allows components to subscribe to context changes without using the Context.Consumer component.
  • Syntax:
    const value = useContext(MyContext);
  • Example:
    import React, { useContext } from 'react'; const ThemeContext = React.createContext('light'); function ThemedButton() { const theme = useContext(ThemeContext); return <button className={theme}>I am styled by theme context!</button>; }

d. useReducer

  • Purpose: An alternative to useState for managing more complex state logic.
  • Syntax:
    const [state, dispatch] = useReducer(reducer, initialState);
  • Example:
    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> Count: {state.count} <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div> ); }

e. useMemo and useCallback

  • useMemo:
    • Purpose: Memoizes the result of a computation to avoid recalculating it on every render.
    • Syntax:
      const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    • Use Case: Expensive calculations that should only re-run when dependencies change.
  • useCallback:
    • Purpose: Memoizes a callback function to prevent it from being recreated on every render.
    • Syntax:
      const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
    • Use Case: Passing stable functions to child components to avoid unnecessary re-renders.

4. Custom Hooks

Custom Hooks allow you to extract and reuse stateful logic across multiple components. They follow the naming convention of starting with use.

  • Example:

    import { useState, useEffect } from 'react'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch(url) .then(response => response.json()) .then(json => { setData(json); setLoading(false); }) .catch(error => { console.error('Error fetching data:', error); setLoading(false); }); }, [url]); return { data, loading }; } export default useFetch;
  • Using Custom Hook:

    import React from 'react'; import useFetch from './useFetch'; function UserProfile() { const { data, loading } = useFetch('https://api.example.com/user'); if (loading) { return <p>Loading...</p>; } return ( <div> <h1>{data.name}</h1> <p>Email: {data.email}</p> </div> ); } export default UserProfile;

5. Rules of Hooks

To ensure Hooks work correctly, follow these rules:

  1. Only Call Hooks at the Top Level: Don’t call Hooks inside loops, conditions, or nested functions. Always use Hooks at the top level of your React function.
  2. Only Call Hooks from React Functions: Call Hooks from React functional components or custom Hooks, not from regular JavaScript functions.

6. Benefits of Using Hooks

  • Simplified Code: Functional components with Hooks are generally simpler and more concise than class components.
  • Reusability: Hooks allow you to reuse stateful logic across multiple components without changing the component hierarchy.
  • Better Separation of Concerns: Hooks enable better organization of related logic, making components easier to maintain.
  • Enhanced Testing: Hooks make it easier to test individual pieces of logic in isolation.
  • Avoid this Keyword: Eliminates the need to bind this in class components, reducing boilerplate and potential bugs.

7. Practical Example: Combining Hooks

Here’s an example of a functional component using multiple Hooks (useState and useEffect):

import React, { useState, useEffect } from 'react'; function Timer() { const [seconds, setSeconds] = useState(0); const [isActive, setIsActive] = useState(false); useEffect(() => { let interval = null; if (isActive) { interval = setInterval(() => { setSeconds(seconds => seconds + 1); }, 1000); } else if (!isActive && seconds !== 0) { clearInterval(interval); } return () => clearInterval(interval); }, [isActive, seconds]); return ( <div> <div>Seconds: {seconds}</div> <button onClick={() => setIsActive(true)}>Start</button> <button onClick={() => setIsActive(false)}>Stop</button> <button onClick={() => setSeconds(0)}>Reset</button> </div> ); } export default Timer;

Explanation:

  • useState: Manages seconds and isActive state variables.
  • useEffect: Sets up a timer that increments seconds every second when isActive is true. It also cleans up the interval when the component unmounts or when isActive changes.

8. Best Practices with Hooks

  • Organize Related Logic Together: Group Hooks that are related to the same piece of logic to enhance readability.
  • Use Custom Hooks for Reusability: Extract reusable logic into custom Hooks to keep components clean.
  • Optimize Performance: Use useMemo and useCallback to prevent unnecessary re-renders.
  • Avoid Overusing State: Only use state for data that influences the rendering of the component. Derived data should be calculated on the fly.
  • Clean Up Side Effects: Always clean up subscriptions or timers in the cleanup function of useEffect to prevent memory leaks.

9. Common Mistakes to Avoid

  • Breaking the Rules of Hooks: Calling Hooks conditionally or inside nested functions can lead to unpredictable behavior.
  • Incorrect Dependency Arrays: Not specifying all dependencies in the useEffect Hook can cause bugs or unintended behaviors.
  • Overusing useState for Complex State: For complex state logic, consider using useReducer instead of multiple useState Hooks.
  • Mutating State Directly: Always use the setter functions provided by Hooks to update state, ensuring immutability.

10. Conclusion

React Hooks have transformed the way developers build and manage state in React applications. By enabling state and side effects in functional components, Hooks promote cleaner, more modular, and reusable code. Mastering Hooks like useState, useEffect, and custom Hooks is essential for modern React development, allowing you to build efficient and maintainable user interfaces with ease.

TAGS
Coding Interview
CONTRIBUTOR
Design Gurus Team

GET YOUR FREE

Coding Questions Catalog

Design Gurus Newsletter - Latest from our Blog
Boost your coding skills with our essential coding questions catalog.
Take a step towards a better tech career now!
Explore Answers
How to clear a behavioral interview?
How can I prepare for coding interview in 2 months?
Which platform is used for React?
Related Courses
Image
Grokking the Coding Interview: Patterns for Coding Questions
Image
Grokking Data Structures & Algorithms for Coding Interviews
Image
Grokking Advanced Coding Patterns for Interviews
Image
One-Stop Portal For Tech Interviews.
Copyright © 2024 Designgurus, Inc. All rights reserved.