DEV Community

Cover image for Demystifying React Hooks: Advanced Usage and Patterns

Demystifying React Hooks: Advanced Usage and Patterns

Welcome, fellow React enthusiasts, to an exhilarating journey through the enchanted forest of React Hooks! 🌳 Today, we're going to take a deep dive into the murky waters of advanced usage and patterns. So strap in, grab your favorite beverage (be it coffee, tea, or a good ol' glass of water), and let's unravel the mysteries together! It's going to be a long one! ⚛️

useState: A Stateful Adventure

Ah, useState - the cornerstone of state management in the land of React. It's like discovering a treasure chest filled with stateful wonders! 💰

import React, { useState } from 'react';

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

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

export default Counter;
Enter fullscreen mode Exit fullscreen mode

With useState, you can sprinkle your functional components with stateful goodness without the fuss of class components. It's like magic, but with fewer rabbits and more JavaScript!

useEffect: Taming Side Effects

Next up, we have useEffect - the master of side effects in the React kingdom. It's like wielding a powerful wand to manage the chaos of asynchronous operations! 🧙‍♂️

Without Dependencies:

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

const RandomFact = () => {
  const [fact, setFact] = useState('');

  useEffect(() => {
    fetch('https://uselessfacts.jsph.pl/random.json?language=en')
      .then(response => response.json())
      .then(data => setFact(data.text));
  }, []);

  return (
    <div>
      <p>Did you know? {fact}</p>
    </div>
  );
};

export default RandomFact;
Enter fullscreen mode Exit fullscreen mode

Here, we fetch a random fact using useEffect without any dependencies, ensuring it runs only once after the initial render.

With Dependencies:

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

const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch(`https://api.example.com/users/${userId}`);
      const userData = await response.json();
      setUser(userData);
    };

    fetchUser();
  }, [userId]);

  return (
    <div>
      {user ? (
        <div>
          <h2>{user.name}</h2>
          <p>Email: {user.email}</p>
          <p>Phone: {user.phone}</p>
        </div>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

export default UserProfile;
Enter fullscreen mode Exit fullscreen mode

In this example, useEffect is dependent on the userId prop, ensuring it updates whenever the prop changes.

useReducer: State Management Wizardry

Behold, useReducer - the sorcerer of state management! It's like wielding a mythical blade to slay the dragons of complex state transitions! 🐉

import React, { useReducer } from 'react';

const initialState = { count: 0 };

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
};

export default Counter;
Enter fullscreen mode Exit fullscreen mode

With useReducer, you can wield the power of complex state management with elegance and grace.

useCallback and useMemo: Memoization Mastery

Introducing useCallback and useMemo - the dynamic duo of memoization magic! They're like the wizards of optimization, casting spells to keep your components lightning-fast! ⚡

With useCallback and useMemo, you can optimize your components by memoizing functions and values, ensuring they're only recalculated when necessary.

useCallback: Memoize Your Functions

Ah, useCallback - the maestro of memoization in the React realm! It's like having a magical spell to optimize your function references, ensuring they stay consistent across renders! 🧙‍♂️

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

const ExpensiveOperation = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    // Do something expensive with count
  }, [count]);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={handleClick}>Do Something Expensive</button>
    </div>
  );
};

export default ExpensiveOperation;
Enter fullscreen mode Exit fullscreen mode

With useCallback, you can memoize your functions to ensure they're only recreated when their dependencies change. This can be particularly useful for optimizing performance in scenarios where expensive calculations or side effects are involved.

When to Use useCallback:

  • Event Handlers: When passing functions as props to child components, especially if they are passed to deeply nested components, useCallback can prevent unnecessary re-renders.
  • Optimizing Expensive Computations: When a function performs expensive computations or relies on external dependencies, memoizing it with useCallback can prevent unnecessary recalculations.

useMemo: Cache Your Values

useMemo - the guardian of value caching in React! It's like having a treasure chest to store your computed values, ensuring they're only recalculated when needed! 💰

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

const MemoizedComponent = ({ a, b }) => {
  const result = useMemo(() => {
    // Perform expensive computation with 'a' and 'b'
    return a + b;
  }, [a, b]);

  return <div>Result: {result}</div>;
};

export default MemoizedComponent;
Enter fullscreen mode Exit fullscreen mode

With useMemo, you can memoize the results of expensive computations, preventing unnecessary re-renders and keeping your components lightning-fast.

When to Use useMemo:

  • Memoizing Expensive Computations: When a component's render method involves heavy calculations or data processing, useMemo can be used to memoize the result, ensuring it's only recalculated when its dependencies change.
  • Optimizing Rendering: When rendering large lists or complex data structures, useMemo can help optimize rendering performance by preventing unnecessary re-renders.

Comparison: useCallback vs useMemo

While both useCallback and useMemo are used for memoization in React, they serve slightly different purposes:

  • useCallback: Memoizes functions and returns a memoized version of the function, ensuring that the function reference remains consistent across renders. Use useCallback when you need to prevent unnecessary re-renders caused by passing new function references down the component tree.

  • useMemo: Memoizes the result of a computation and returns a cached version of the result, recalculating it only when its dependencies change. Use useMemo when you need to optimize expensive computations or data processing within your components.

Real-Life Example:

Imagine you have a complex form component where you have event handlers for various inputs. In this scenario, you can use useCallback to memoize the event handlers to prevent unnecessary re-renders caused by passing new function references down to child components.

On the other hand, if your form component has computed values based on user inputs or external data, you can use useMemo to memoize these values, ensuring they're only recalculated when necessary, thus optimizing the performance of your component.

I am planning to create a separate post going in details for these two. It will be a part of the "Demystifing React: Towards Intermediate Concepts" series so make sure to follow! 🧑‍💻

With useCallback and useMemo at your disposal, you now wield the power to optimize your React components like never before! Choose wisely, and may your components be lightning-fast and jank-free! ⚡️✨

useRef: Keeping Tabs on the DOM

Enter useRef - the guardian of mutable values in the land of React. It's like having a loyal squire to keep watch over your DOM elements! 🛡️

import React, { useRef } from 'react';

const FocusInput = () => {
  const inputRef = useRef();

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
};

export default FocusInput;
Enter fullscreen mode Exit fullscreen mode

With useRef, you can easily access and manipulate DOM elements, manage focus, and even store persistent values between renders.

useLayoutEffect: Synchronizing with the Browser

Introducing useLayoutEffect, the virtuoso of synchronous updates. It's like having a backstage pass to the render lifecycle! 🎭

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

const MeasureWindow = () => {
  const [width, setWidth] = useState(0);

  useLayoutEffect(() => {
    const updateWidth = () => {
      setWidth(window.innerWidth);
    };

    window.addEventListener('resize', updateWidth);
    updateWidth(); // Initial measurement

    return () => window.removeEventListener('resize', updateWidth);
  }, []);

  return <div>Window Width: {width}px</div>;
};

export default MeasureWindow;
Enter fullscreen mode Exit fullscreen mode

With useLayoutEffect, you can perform DOM mutations and measurements synchronously before the browser has a chance to paint, ensuring a smooth and jank-free user experience.

Bonus Round: useNavigate and useLocation

And now, for our bonus round, featuring useNavigate and useLocation - the dynamic duo of React Router v6! 🚀

import { useNavigate, useLocation } from 'react-router-dom';

const NavigationExample = () => {
  const navigate = useNavigate();
  const location = useLocation();

  const handleClick = () => {
    navigate('/new-route');
  };

  return (
    <div>
      <p>Current URL: {location.pathname}</p>
      <button onClick={handleClick}>Navigate to New Route</button>
    </div>
  );
};

export default NavigationExample;
Enter fullscreen mode Exit fullscreen mode

With useNavigate and useLocation, you can build dynamic, single-page application-style navigation experiences with ease. But wait, there's more! Let's level up our example by passing state and accessing it:

import { useNavigate, useLocation } from 'react-router-dom';

const NavigationWithState = () => {
  const navigate = useNavigate();
  const location = useLocation();

  const handleClick = () => {
    navigate('/new-route', { state: { exampleState: 'Hello, React!' } });
  };

  return (
    <div>
      <p>Current URL: {location.pathname}</p>
      <button onClick={handleClick}>Navigate to New Route with State</button>
    </div>
  );
};

export default NavigationWithState;
Enter fullscreen mode Exit fullscreen mode

In this enhanced example, we utilize the state option in the navigate function to pass state to the new route. Now, let's access this state in the destination component:

import { useLocation } from 'react-router-dom';

const NewRouteComponent = () => {
  const location = useLocation();
  const exampleState = location.state.exampleState;

  return (
    <div>
      <p>Received State: {exampleState}</p>
    </div>
  );
};

export default NewRouteComponent;
Enter fullscreen mode Exit fullscreen mode

By accessing location.state in the destination component, we can retrieve the state passed from the previous route, enabling seamless communication between components across routes.


And there you have it, brave adventurers - a grand tour through the mystical realms of React Hooks 🎣! From state management sorcery to optimization wizardry, we've covered it all with examples and a sprinkle of magic. In the next post, we will touch beyond our friends useState and useEffect 🧑‍💻!

So go forth, wield your hooks with confidence, and may your React journey be filled with wonder and joy! ✨🔮

Top comments (0)