DEV Community

Cover image for 10 React Hooks with Example
Niraj Narkhede
Niraj Narkhede

Posted on

10 React Hooks with Example

Introduction: Embracing the Power of React Hooks

Hey there, fellow UI developer! Are you ready to dive into the exciting world of React Hooks? If you've been working with React for a while, you might remember the days when class components were the go-to for managing state and side effects. But times have changed, and React Hooks have revolutionized the way we build components.

In this friendly guide, we'll explore 10 essential React Hooks, complete with example tutorials to help you understand and implement them in your projects. Whether you're new to Hooks or looking to deepen your knowledge, this post has got you covered. So, grab your favorite beverage, get comfortable, and let's embark on this React Hooks adventure together!

1. useState: Managing State with Ease

Let's kick things off with the most commonly used Hook: useState. This little gem allows you to add state to your functional components without the need for classes.

How it works

The useState Hook returns an array with two elements: the current state value and a function to update it. Here's a simple example:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're creating a counter that increases every time the button is clicked. The useState Hook initializes our count to 0, and we use the setCount function to update it.

When to use useState

  • When you need to manage local state in a functional component
  • For simple data types like numbers, strings, or booleans
  • When you want to avoid the complexity of class components for basic state management

2. useEffect: Handling Side Effects

Next up is useEffect, the Hook that lets you perform side effects in your components. It's like componentDidMount, componentDidUpdate, and componentWillUnmount all rolled into one!

How it works

useEffect takes two arguments: a function to run after render, and an optional array of dependencies. Here's an example:

import React, { useState, useEffect } from 'react';

function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);

    // Cleanup function
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Empty dependency array means this effect runs once on mount

  return <div>Window width: {width}px</div>;
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using useEffect to add an event listener for window resizing. The cleanup function removes the listener when the component unmounts.

When to use useEffect

  • For data fetching
  • Setting up subscriptions or event listeners
  • Manually changing the DOM
  • Logging or any other side effects that don't directly impact the render

3. useContext: Consuming Context with Ease

The useContext Hook provides a way to consume context in functional components without the need for render props or higher-order components.

How it works

First, you create a context using React.createContext(), then use the useContext Hook to consume it:

import React, { useContext } from 'react';

const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>I'm styled by theme context!</button>;
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, ThemedButton uses the useContext Hook to access the current theme value from ThemeContext.

When to use useContext

  • When you need to share data that can be considered "global" for a tree of React components
  • To avoid prop drilling (passing props through multiple levels of components)
  • For theming, user authentication, or any other application-wide data

4. useReducer: Managing Complex State Logic

When useState isn't enough, useReducer comes to the rescue. It's particularly useful for managing more complex state logic.

How it works

useReducer takes a reducer function and an initial state, and returns the current state paired with a dispatch method:

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: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using useReducer to manage a counter with increment and decrement actions.

When to use useReducer

  • When you have complex state logic that involves multiple sub-values
  • When the next state depends on the previous one
  • When you want to optimize performance for components that trigger deep updates

5. useCallback: Optimizing Performance

The useCallback Hook can help you optimize the performance of your components by memoizing callback functions.

How it works

useCallback returns a memoized version of the callback that only changes if one of the dependencies has changed:

import React, { useState, useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return (
    <div>
      <ChildComponent onIncrement={increment} />
      <p>Count: {count}</p>
    </div>
  );
}

function ChildComponent({ onIncrement }) {
  console.log('ChildComponent rendered');
  return <button onClick={onIncrement}>Increment</button>;
}
Enter fullscreen mode Exit fullscreen mode

In this example, the increment function is memoized with useCallback, preventing unnecessary re-renders of ChildComponent.

When to use useCallback

  • When passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders
  • In combination with useMemo for creating memoized callbacks

6. useMemo: Memoizing Expensive Computations

Similar to useCallback, useMemo is used for optimization, but it memoizes the result of a computation.

How it works

useMemo takes a function and an array of dependencies, and only recomputes the memoized value when one of the dependencies has changed:

import React, { useState, useMemo } from 'react';

function ExpensiveComponent({ list }) {
  const [filter, setFilter] = useState('');

  const filteredList = useMemo(() => {
    console.log('Filtering list...');
    return list.filter(item => item.toLowerCase().includes(filter.toLowerCase()));
  }, [list, filter]);

  return (
    <div>
      <input
        type="text"
        value={filter}
        onChange={e => setFilter(e.target.value)}
        placeholder="Filter list"
      />
      <ul>
        {filteredList.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using useMemo to memoize the filtered list, preventing unnecessary recalculations on every render.

When to use useMemo

  • For expensive calculations that don't need to be re-run on every render
  • When you want to avoid re-rendering child components unnecessarily
  • For referential equality checks in other Hooks' dependency arrays

7. useRef: Accessing DOM Elements and Storing Mutable Values

The useRef Hook provides a way to create a mutable reference that persists across re-renders.

How it works

useRef returns a mutable ref object whose .current property is initialized to the passed argument:

import React, { useRef, useEffect } from 'react';

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using useRef to get a reference to the input element and focus it when the component mounts.

When to use useRef

  • To access DOM elements directly
  • For storing mutable values that don't cause re-renders when updated
  • For keeping track of previous values in functional components

8. useImperativeHandle: Customizing Instance Value

useImperativeHandle customizes the instance value that is exposed to parent components when using ref.

How it works

useImperativeHandle should be used with forwardRef:

import React, { useRef, useImperativeHandle, forwardRef } from 'react';

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    getValue: () => {
      return inputRef.current.value;
    }
  }));

  return <input ref={inputRef} />;
});

function Parent() {
  const fancyInputRef = useRef();

  const handleClick = () => {
    fancyInputRef.current.focus();
    console.log(fancyInputRef.current.getValue());
  };

  return (
    <div>
      <FancyInput ref={fancyInputRef} />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using useImperativeHandle to customize what instance value is exposed to the parent component.

When to use useImperativeHandle

  • When you want to customize the exposed instance value of a forwardRef component
  • To limit the exposed functionality of a child component to its parent

9. useLayoutEffect: Synchronous Effect Hook

useLayoutEffect is similar to useEffect, but it fires synchronously after all DOM mutations.

How it works

The signature is identical to useEffect, but it fires synchronously before the browser has a chance to paint:

import React, { useState, useLayoutEffect } from 'react';

function Tooltip() {
  const [tooltipHeight, setTooltipHeight] = useState(0);
  const tooltipRef = useRef();

  useLayoutEffect(() => {
    const height = tooltipRef.current.clientHeight;
    setTooltipHeight(height);
  }, []);

  return (
    <div>
      <div ref={tooltipRef}>Tooltip content</div>
      <p>The tooltip height is: {tooltipHeight}px</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using useLayoutEffect to measure the height of a DOM element synchronously before the browser paints.

When to use useLayoutEffect

  • When you need to make DOM measurements or mutations that should be applied synchronously before the browser paints
  • For animations that require measurements of DOM elements
  • When you want to avoid flickering caused by asynchronous updates

10. useDebugValue: Labeling Custom Hooks for DevTools

Last but not least, useDebugValue can be used to display a label for custom hooks in React DevTools.

How it works

useDebugValue accepts a value and an optional formatting function:

import React, { useState, useDebugValue } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ... Logic to determine if the friend is online ...

  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using useDebugValue to display the friend's online status in React DevTools.

When to use useDebugValue

  • In custom Hooks to provide more context about the Hook's state
  • For debugging complex custom Hooks
  • To improve the developer experience when working with custom Hooks

Conclusion: Mastering React Hooks

Wow, we've covered a lot of ground! From managing state with useState to optimizing performance with useMemo and useCallback, React Hooks offer a powerful and flexible way to build UI components. Let's recap the 10 Hooks we've explored:

  1. useState: For managing local state
  2. useEffect: For handling side effects
  3. useContext: For consuming context
  4. useReducer: For managing complex state logic
  5. useCallback: For optimizing performance of callbacks
  6. useMemo: For memoizing expensive computations
  7. useRef: For accessing DOM elements and storing mutable values
  8. useImperativeHandle: For customizing instance value
  9. useLayoutEffect: For synchronous effect execution
  10. useDebugValue: For labeling custom Hooks in DevTools

Remember, the key to mastering React Hooks is practice. Start by incorporating them into your projects one at a time. As you become more comfortable, you'll find that Hooks can significantly simplify your code and make your components more reusable and easier to understand.

Don't be afraid to experiment and combine different Hooks to solve complex problems. The React community is constantly coming up with new patterns and custom Hooks, so keep learning and sharing your discoveries!

I hope this friendly guide has helped you get a better grasp on React Hooks. Happy coding, and may your components be forever functional and hook-tastic!

"Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class." - React Documentation

Now go forth and hook it up! ๐ŸŽฃ๐Ÿš€

Top comments (0)