Exploring lesser-known React hooks and their importance
Tags: react, hooks, javascript, programming
Published: 2023-09-01 17:17:50

Hooks which were introduced in [React version 16.8](https://legacy.reactjs.org/blog/2019/02/06/react-v16.8.0.html), provide function components access to React's state and lifecycle features. Hooks make components more organized and reusable by enabling developers to divide components into smaller parts that can be joined easily.
The most commonly used hooks which are [`useState`](https://react.dev/reference/react/useState) and [`useEffect`](https://react.dev/reference/react/useEffect) are useful in React apps as `useState` allows developers to add state to functional components, while `useEffect` helps manage side effects such as data fetching and DOM updates. Both hooks can be combined to carry out complex tasks.
Although these fundamental hooks are necessary for creating React applications, this article focuses on uncovering lesser-known React hooks that offer specialized solutions for improving performance and simplifying code.

## useContext
The [useContext hook](https://react.dev/reference/react/useContext) is a fundamental part of React that provides a better way to manage state globally. It provides a way to share state across multiple nested components without needing to pass props through every level of the component tree.

### Use case: counter app
When a state is passed down nested components without the use of the `useContext` hook like in the example below, it results in what is called "prop drilling".

```javascript
import React, { useState } from 'react';

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

  return (
    <div>
      <h1>Counter</h1>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
      <ParentComponent count={count}/>
    </div>
  );
};

function Counterchild ({ count }) {
  return (
    <div>
      <h2>Counter Child</h2>
      <ChildComponent count={count}/>
    </div>
  );
};

function Counterdisplay ({ count }) {
  return (
    <div>
      <h3>Counter Display</h3>
      <p>Count: {count}</p>
    </div>
  );
};

export default Counter;

```
In this example, the `count` state is defined in the `Counter` component, and it's passed down as props through the `Counterchild` to the `Counterdisplay`. Each component receives the `count` state as a prop, allowing them to display it's value.
Although this method is effective, it becomes lengthy when the state is passed down through many nested child components.
To use the `useContext` hook, we first import `createContext` and initialize it.

```javascript
import { useState, createContext } from "react";

const count = createContext()
```
Next, we'll wrap the tree of components that require the state with the context provider.

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

  return (
<UserContext.Provider value={user}>
    <div>
      <h1>Counter</h1>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
      <Counterchild count={count}/>
    </div>
</UserContext.Provider>
  );
};

```
To make use of the previously created context located in the parent component, we need to import `useContext`.

```javascript
import { useContext } from "react";
```
Now, our context is available for use in the child component.

```javascript
function Counterdisplay() {
  const user = useContext(count);

  return (
    <div>
      <h3>Counter Display</h3>
      <p>Count: {count}</p>
    </div>
  );
}
```

## useRef
The [useRef hook](https://react.dev/reference/react/useRef) allows us to create a mutable reference to a value or a DOM element. Unlike the `useState` hook, useRef doesn't cause a re-render when its value changes.
`useRef` accepts one argument as the initial value and returns one object called `current`. For example:

```javscript
const initialValue = ''
const reference = useRef(initialValue)
```
A reference can be accessed and changed using the `current` object:

```javascript
  useEffect(() => {
    reference.current = 'My Reference';
  });
```
### Use case 1: Tracking application re-renders
We would start an infinite loop if we attempted to count how many times our application renders using the `useState` hook as it causes a re-render itself. To avoid this, we can use the `useRef` Hook:

```javascript
import { useState, useEffect, useRef } from "react";

function App() {
  const [inputValue, setInputValue] = useState("");
  const count = useRef(0);

  useEffect(() => {
    count.current = count.current + 1;
  });

  return (
    <>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h1>Render Count: {count.current}</h1>
    </>
  );
}
```
In the example above we set the initial value of the reference with `useRef(0)` and access the value of count using `count.current`.
### Use case 2: Focusing the input element
`useRef` can be used to hold a reference to a DOM element, we can then directly access and perform functions on this element by carrying out the following:

1. Define the reference to be used in accessing the element

2. Assign the reference to the `ref` attribute of the element to be interacted with


```javascript
import { useRef } from "react";

function App() {
  const inputElement = useRef();

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

  return (
    <>
      <input type="text" ref={inputElement} />
      <button onClick={focusInput}>Focus Input</button>
    </>
  );
}
```
In the example above, the reference that was created was assigned to the `input` tag, and we were able to focus the element using the reference assigned to it.

### Limitations
Updating a reference should be done inside a `useEffect` callback or inside handlers (event handlers, timer handlers, etc.), not inside the immediate scope of the functional component's function, as the function scope of the functional component should either calculate the output or invoke hooks.

```javascript
import { useRef } from "react";

function App() {
  const count = useRef(0);
      useEffect(() => {
    count.current++; // Success

    setTimeout(() => {
      count.current++; // Success
    }, 1000);
  }, []);

  count.current++; // Error
}

```

## useReducer
The [useReducer hook](https://react.dev/reference/react/useReducer) is used for managing complex state logic in a more organized way. It's an alternative to `useState` and is particularly useful when state changes entail numerous actions.
`useReducer` takes in a reducer function and an initial state. It returns the current state and a dispatch function that you can use to send actions to update the state based on the logic defined in the reducer function

### Use case: Creating a counter

```javascript
import { useReducer } from 'react';

// Reducer function
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

function Counter() {
  const initialState = { count: 0 };
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
    </div>
  );
}

export default Counter;
```
In this example, the reducer function handles different actions ('INCREMENT' and 'DECREMENT') to update the state. The `useReducer` hook initializes the state with `{ count: 0 }` and provides a dispatch function to trigger these actions.
When we click the "Increment" button, it dispatches an action of type 'INCREMENT', which causes the state to be updated and the count to increase. Similarly, clicking the "Decrement" button dispatches an action of type 'DECREMENT', reducing the count.

## useCallback
The [useCallback hook](https://react.dev/reference/react/useCallback) is primarily used to memoize functions, preventing their needless re-creation on each render. This can be very helpful when sending functions to child components as props. 
### Use case: Improving performance
The `useCallback`'s basic syntax is as follows:
```javascript
import React, { useState, useCallback } from 'react';

function ChildComponent({ handleClick }) {
  console.log('ChildComponent rendered');
  return <button onClick={handleClick}>Click me</button>;
}

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

  // Using useCallback to memoize the function
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  console.log('ParentComponent rendered');

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


```
The `increment` function is memoized because the `useCallback` hook ensures the function remains the same between renders if the dependencies (`count` in this case) haven't changed. This prevents the unnecessary re-creation of the increment function when the `ParentComponent` re-renders.
The increment function is then passed as the `handleClick` prop to the `ChildComponent`. Thanks to `useCallback`, the `handleClick` prop will remain stable between renders, even as the `ParentComponent` updates its state.

## useMemo
The [useMemo](https://react.dev/reference/react/useMemo) and `useCallback` hooks have some similarities. The main difference is `useMemo` returns a memoized value whereas `useCallback` returns a memoized function.

### Use case: Improving performance
The `useMemo` Hook can be used to keep expensive, resource intensive functions from needlessly running, thereby improving the performance of the React app.

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

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

  // Using useMemo to optimize a calculation
  const squaredCount = useMemo(() => {
    console.log('Calculating squaredCount');
    return count * count;
  }, [count]);

  console.log('ParentComponent rendered');

  return (
    <div>
      <p>Count: {count}</p>
      <p>Squared Count: {squaredCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default ParentComponent;

```
In this example, the `useMemo` hook is used to optimize the calculation of the `squaredCount` value and the calculation depends on the count state. By specifying `[count]` as the dependency array, we ensure that the calculation is only recomputed when the count state changes.

When you click the "Increment" button, the count state is updated, triggering a re-render of the `ParentComponent`. However, thanks to the `useMemo` optimization, the calculation of `squaredCount` only occurs when the count state changes, avoiding unnecessary recalculations.

## Conclusion
React hooks are crucial tools for front-end development for creating dynamic and effective applications. This article delves into lesser-known hooks like "useContext," "useRef," "useReducer," "useCallback," and "useMemo" that provide solutions for various challenges. I urge you to experiment with and include these hooks in your projects to attain a better understanding of React's capabilities.

## Resources

- [Complete React hooks documentation](https://react.dev/reference/react)