React Hooks and Their Uses: A Must Read

React Hooks and Their Uses: A Must Read

Introduction -

React has revolutionized how we build user interfaces by providing a component-based architecture that encourages reusability and modularity. However, managing state and side effects within components has always been a challenge. Enter React Hooks. Introduced in React 16.8, Hooks allow you to use state and other React features without writing a class. They simplify the logic and enhance the readability of your components. In this blog, we’ll explore all the built-in React Hooks and how to create your own custom Hook to harness the full power of React.


The Basics: Built-in React Hooks

1. useState

The useState Hook is the cornerstone of state management in functional components. It allows you to add state variables to your components.

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>
  );
}

2. useEffect

The useEffect Hook lets you perform side effects in your components, such as fetching data, directly interacting with the DOM, or subscribing to services.

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(data => setData(data));
  }, []);

  return <div>{data ? data.message : 'Loading...'}</div>;
}

3. useContext

The useContext Hook allows you to access the context object, making it easier to share data across the component tree without prop drilling.

import React, { useContext } from 'react';

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

function ThemeButton() {
  const theme = useContext(ThemeContext);

  return <button style={{ background: theme === 'dark' ? '#333' : '#FFF' }}>Theme Button</button>;
}

4. useReducer

The useReducer Hook is an alternative to useState for managing more complex state logic. It’s similar to Redux but integrated directly into your components.

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 (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

5. useCallback

The useCallback Hook returns a memoized version of the callback function that only changes if one of the dependencies has changed. It’s useful for optimizing performance.

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

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

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <Child onClick={handleClick} />
    </div>
  );
}

function Child({ onClick }) {
  return <button onClick={onClick}>Click me</button>;
}

6. useMemo

The useMemo Hook returns a memoized value that only recomputes when one of the dependencies changes. It’s used to optimize performance by avoiding expensive calculations on every render.

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

function ExpensiveCalculation({ num }) {
  const result = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < num; i++) {
      sum += i;
    }
    return sum;
  }, [num]);

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

7. useRef

The useRef Hook is used to persist values across renders without causing a re-render. It’s often used to access DOM elements directly.

import React, { useRef } from 'react';

function TextInput() {
  const inputRef = useRef();

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

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

8. useLayoutEffect

The useLayoutEffect Hook runs synchronously after all DOM mutations but before the browser has a chance to paint. It’s used for reading layout from the DOM and synchronously re-rendering.

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

function LayoutEffectComponent() {
  const [height, setHeight] = useState(0);
  const ref = useRef();

  useLayoutEffect(() => {
    setHeight(ref.current.clientHeight);
  });

  return (
    <div ref={ref} style={{ height: '100px' }}>
      <p>Height: {height}</p>
    </div>
  );
}

9. useImperativeHandle

The useImperativeHandle Hook customizes the instance value that is exposed when using ref in a parent component. It’s used in conjunction with forwardRef.

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

const CustomInput = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));

  const inputRef = useRef();

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

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

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus Input</button>
    </div>
  );
}

10. useDebugValue

The useDebugValue Hook is used to display a label for custom hooks in React DevTools, making it easier to debug custom hook behavior.

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

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

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

  useEffect(() => {
    // Imagine this subscribes to a friend's status
    setIsOnline(friendID % 2 === 0); // Just a mock
  }, [friendID]);

  return isOnline;
}

Creating Custom Hooks

Custom Hooks let you extract component logic into reusable functions. They follow the same principles as built-in Hooks and can use other Hooks inside them.

Example: useFetch

Let’s create a custom Hook useFetch that fetches data from an API.

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      const response = await fetch(url);
      const result = await response.json();
      setData(result);
      setLoading(false);
    };

    fetchData();
  }, [url]);

  return { data, loading };
}

// Usage
import React from 'react';

function App() {
  const { data, loading } = useFetch('https://api.example.com/data');

  if (loading) return <p>Loading...</p>;

  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default App;

Example: useLocalStorage

Another example is useLocalStorage, which syncs state with localStorage.

import { useState } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = value => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

// Usage
import React from 'react';

function App() {
  const [name, setName] = useLocalStorage('name', 'Anonymous');

  return (
    <div>
      <input
        type="text"
        value={name