Sobes.tech
Junior

What hooks are available in React?

sobes.tech AI

Answer from AI

Starting from version 16.8, hooks in React provide functional components with the ability to manage state and use lifecycle methods that were previously only available to class components.

Here are the main built-in hooks:

  • useState: Allows adding internal state to a functional component. Returns a pair: the current state and a function to update it.

    // Example useState
    import React, { useState } from 'react';
    
    function Counter() {
      const [count, setCount] = useState(0); // Initialize state with 0
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    
  • useEffect: Allows performing side effects in functional components. Similar to lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount).

    // Example useEffect
    import React, { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      // Runs after every render
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      });
    
      // Runs only on the first render (empty dependency array)
      useEffect(() => {
        console.log('Component mounted');
        return () => {
          console.log('Component unmounted'); // Cleanup function
        };
      }, []);
    
      // Runs when count changes
      useEffect(() => {
        console.log('Count changed:', count);
      }, [count]);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    
  • useContext: Allows subscribing to React context changes. Takes a context object created with React.createContext().

    // Example useContext
    import React, { useContext } from 'react';
    
    const ThemeContext = React.createContext('light'); // Creating context
    
    function ThemedButton() {
      const theme = useContext(ThemeContext); // Using context
    
      return (
        <button className={theme}>
          Button in {theme} theme
        </button>
      );
    }
    
    function App() {
      return (
        <ThemedButton />
      );
    }
    
  • useReducer: Alternative to useState for managing more complex state that involves multiple sub-values or when the next state depends on the previous one. Similar to reducers in Redux.

    // Example useReducer
    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:
          throw new Error();
      }
    }
    
    function Counter() {
      const [state, dispatch] = useReducer(reducer, initialState);
    
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({ type: 'increment' })}>+</button>
          <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
        </>
      );
    }
    
  • useCallback: Memoizes a callback function. Returns a memoized version of the callback that only changes if one of the dependencies has changed. Useful for preventing unnecessary re-renders of child components when passing callbacks.

    // Example useCallback
    import React, { useState, useCallback } from 'react';
    
    function Parent({ addTodo }) {
      const [count, setCount] = useState(0);
    
      const handleAddTodo = useCallback(() => {
        addTodo();
      }, [addTodo]); // Dependency on addTodo
    
      return (
        <>
          <button onClick={() => setCount(count + 1)}>
            Increase count
          </button>
          <Child onAddItem={handleAddTodo} />
        </>
      );
    }
    
    function Child({ onAddItem }) {
      console.log('Child renders'); // Will only render when onAddItem changes
      return <button onClick={onAddItem}>Add item</button>;
    }
    
  • useMemo: Memoizes a computed value. Returns a memoized value that is recalculated only when one of the dependencies changes. Useful for performance optimization with expensive calculations.

    // Example useMemo
    import React, { useMemo, useState } from 'react';
    
    function expensiveCalculation(num) {
      console.log('Performing expensive calculation...');
      for (let i = 0; i < 1000000; i++) {
        num += 1;
      }
      return num;
    }
    
    function Example() {
      const [count, setCount] = useState(0);
      const [todos, setTodos] = useState([]);
    
      const calculation = useMemo(() => expensiveCalculation(count), [count]); // Dependency on count
    
      const addTodo = () => {
        setTodos(t => [...t, "New Todo"]);
      };
    
      return (
        <div>
          <div>
            <h2>My Todos</h2>
            {todos.map((todo, index) => (
              <p key={index}>{todo}</p>
            ))}
            <button onClick={addTodo}>Add Todo</button>
          </div>
          <hr />
          <div>
            Count: {count}
            <button onClick={() => setCount(c => c + 1)}>+</button>
            <h2>Expensive Calculation</h2>
            {calculation}
          </div>
        </div>
      );
    }
    
  • useRef: Creates a mutable ref that persists for the lifetime of the component. Often used to access DOM elements or store mutable values that do not cause re-renders when changed.

    // Example useRef
    import React, { useRef } from 'react';
    
    function TextInputWithFocusButton() {
      const inputEl = useRef(null); // Create ref
    
      const onButtonClick = () => {
        // current points to the mounted DOM element
        inputEl.current.focus();
      };
    
      return (
        <>
          <input ref={inputEl} type="text" /> // Attach ref
          <button onClick={onButtonClick}>Focus the input</button>
        </>
      );
    }
    
  • useImperativeHandle: Customizes the instance value that is exposed to parent components when using ref. Allows a child component to expose certain methods to its parent. Used with forwardRef.

    // Example useImperativeHandle
    import React, { useRef, useImperativeHandle, forwardRef } from 'react';
    
    const MyInput = forwardRef((props, ref) => {
      const inputRef = useRef();
    
      useImperativeHandle(ref, () => ({
        focusInput: () => {
          inputRef.current.focus();
        }
      }));
    
      return <input ref={inputRef} {...props} />;
    });
    
    function ParentComponent() {
      const inputRef = useRef();
    
      const handleFocus = () => {
        inputRef.current.focusInput(); // Call method from child component
      };
    
      return (
        <div>
          <MyInput ref={inputRef} />
          <button onClick={handleFocus}>Focus via Parent</button>
        </div>
      );
    }
    
  • useLayoutEffect: Similar to useEffect, but runs synchronously after all DOM mutations. Useful for reading layout from the DOM and re-rendering synchronously. Can block visual updates.

    // Example useLayoutEffect
    import React, { useLayoutEffect, useRef } from 'react';
    
    function Tooltip({ children, text }) {
      const ref = useRef(null);
    
      useLayoutEffect(() => {
        if (ref.current) {
          // Perform DOM measurements before the browser repaints
          const { top, height } = ref.current.getBoundingClientRect();
          console.log(`Element is at ${top} with height ${height}`);
        }
      }, [children]);
    
      return (
        <span ref={ref} title={text}>
          {children}
        </span>
      );
    }
    
  • useDebugValue: Displays a label for custom hooks in React DevTools. Used for debugging custom hooks.

    // Example useDebugValue
    import React, { useState, useDebugValue } from 'react';
    
    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      // Displays 'Friend Status: Offline' or 'Friend Status: Online' in DevTools
      useDebugValue(isOnline === true ? 'Online' : 'Offline');
    
      // ... logic to fetch friend's status
    
      return isOnline;
    }
    
    function FriendListItem({ friend }) {
      const isOnline = useFriendStatus(friend.id);
    
      return (
        <li style={{ color: isOnline ? 'green' : 'black' }}>
          {friend.name} - {isOnline ? 'Online' : 'Offline'}
        </li>
      );
    }
    

In addition to built-in hooks, there are custom hooks. These are JavaScript functions whose names start with "use". They allow reusing stateful logic between components. A custom hook can call other hooks.

// Example custom hook
import { useState, useEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState([window.innerWidth, window.innerHeight]);

  useEffect(() => {
    const handleResize = () => {
      setSize([window.innerWidth, window.innerHeight]);
    };
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return size;
}

function MyComponent() {
  const [width, height] = useWindowSize(); // Using custom hook

  return (
    <p>Window size: {width}x{height}</p>
  );
}

It is important to follow Rules of Hooks:

  1. Call hooks only at the top level of React functions or custom hooks. Do not call hooks inside loops, conditions, or nested functions.
  2. Call hooks only from React functional components or custom hooks. Do not call them from regular JavaScript functions or class methods.