5 Great Custom Hooks For Your React Project
Tags: react, reacthooks, typescript, frontend
Published: 2023-02-23 22:30:02

As I was searching through the codebase and writing the components for my current work ticket, I came across some useful custom hooks from a number of third-party libraries like [react-use](https://github.com/streamich/react-use), [usehooks-ts](https://github.com/juliencrn/usehooks-ts), [Mantine](https://mantine.dev/). But there are for sure other similar libraries out there such as [use-http](https://github.com/ava/use-http), [react-hanger](https://github.com/kitze/react-hanger) and many more.

## 1️⃣ [useTitle](https://github.com/streamich/react-use/blob/master/src/useTitle.ts)

This hook from [react-use](https://github.com/streamich/react-use) allows you to dynamically set the title of a page in your application. It can be used to set the title of the page to a specific value or to update the title in response to user interactions or state changes in the application.

You may think that there's nothing particularly special about that since you could easily do `document.title` and you're good to go, but under the hood, the hook checks that the document is not undefined and since it's a side effect it implements the `useEffect` hook:

```typescript
import { useEffect, useRef } from "react";

export interface UseTitleOptions {
  restoreOnUnmount?: boolean;
}

const DEFAULT_USE_TITLE_OPTIONS: UseTitleOptions = {
  restoreOnUnmount: false,
};

function useTitle(
  title: string,
  options: UseTitleOptions = DEFAULT_USE_TITLE_OPTIONS
) {
  const prevTitleRef = useRef(document.title);
  
  if (document.title !== title) document.title = title;

  useEffect(() => {
    if (options && options.restoreOnUnmount) {
      return () => {
        document.title = prevTitleRef.current;
      };
    } else {
      return;
    }
  }, []);
}

export default typeof document !== "undefined"
  ? useTitle
  : (_title: string) => {};
```

### Usage:

```typescript
import { useTitle } from 'react-use';

function MyComponent() {
  const [title, setTitle] = useTitle('Old Title');

  const handleClick = () => setTitle('New Title');

  return (
    <div>
      <h1>{title}</h1>
      <button onClick={handleClick}>Change Title</button>
    </div>
  );
}
```

An instance of the `useTitle` hook is used to set the initial value of the `title` state variable to `'Default Title'`. The setter function `setTitle` is then called to update the title on clicking the button.

## 2️⃣ [useCopyToClipboard](https://github.com/juliencrn/usehooks-ts/blob/master/src/useCopyToClipboard/useCopyToClipboard.ts)

This implementation of the `useCopyToClipboard` hook comes from the [usehooks-ts](https://github.com/juliencrn/usehooks-ts) library. It provides an easy way to copy text to the clipboard. Other React hooks libraries also have this hook which they implement differently: some rely on JavaScript libraries like copy-to-clipboard that run on `document.execCommand()` while others like this one depend on the `clipboard` API.

```typescript
import { useState } from 'react'

type CopiedValue = string | null
type CopyFn = (text: string) => Promise<boolean> // Return success

function useCopyToClipboard(): [CopiedValue, CopyFn] {
  const [copiedText, setCopiedText] = useState<CopiedValue>(null)

  const copy: CopyFn = async text => {
    if (!navigator?.clipboard) {
      console.warn('Clipboard not supported')
      return false
    }

    // Try to save to clipboard then save it in the state if worked
    try {
      await navigator.clipboard.writeText(text)
      setCopiedText(text)
      return true
    } catch (error) {
      console.warn('Copy failed', error)
      setCopiedText(null)
      return false
    }
  }

  return [copiedText, copy]
}

export default useCopyToClipboard
```

The clipboard API does the cut, copy and paste asynchronously, which is why `writeText()` is called in a `try-catch` block. The state variable `copiedText` holds the value or `null` depending on the outcome of the asynchronous action.

### Usage

```typescript
import { useCopyToClipboard } from 'usehooks-ts'

export default function Component() {
  const [value, copy] = useCopyToClipboard()
  return (
    <>
      <h1>Click to copy:</h1>
      <div style={{ display: 'flex' }}>
        <button onClick={() => copy('A')}>A</button>
        <button onClick={() => copy('B')}>B</button>
        <button onClick={() => copy('C')}>C</button>
      </div>
      <p>Copied value: {value ?? 'Nothing is copied yet!'}</p>
    </>
  )
}
```

In this example, the letters passed to the setter get copied to the user's clipboard on clicking the buttons. Then `value` is conditionally displayed in the `p` element below.

## 3️⃣ [useOs](https://mantine.dev/hooks/use-os/)

Have you ever thought about designing or making your application behave differently based on the user's operating system (OS) such as implementing adaptive design or providing different download links for different platforms? Well, this hook from the [Mantine](https://mantine.dev/) library provides you with the OS name, returning it as a `string`.

```typescript
export type OS = 'undetermined' | 'macos' | 'ios' | 'windows' | 'android' | 'linux';

function getOS(): OS {
  const { userAgent } = window.navigator;
  const macosPlatforms = /(Macintosh)|(MacIntel)|(MacPPC)|(Mac68K)/i;
  const windowsPlatforms = /(Win32)|(Win64)|(Windows)|(WinCE)/i;
  const iosPlatforms = /(iPhone)|(iPad)|(iPod)/i;

  if (macosPlatforms.test(userAgent)) {
    return 'macos';
  }
  if (iosPlatforms.test(userAgent)) {
    return 'ios';
  }
  if (windowsPlatforms.test(userAgent)) {
    return 'windows';
  }
  if (/Android/i.test(userAgent)) {
    return 'android';
  }
  if (/Linux/i.test(userAgent)) {
    return 'linux';
  }

  return 'undetermined';
}

export function useOs(): OS {
  if (typeof window !== 'undefined') {
    return getOS();
  }

  return 'undetermined';
}
```

Possible values are `undetermined`, `macos`, `ios`, `windows`, `android`, `linux`. If os cannot be identified, for example, during server-side rendering (since the window object would be undefined) `undetermined` will be returned.

### Usage:

```typescript
import { useOs } from '@mantine/hooks';

function Demo() {
  const os = useOs();
  return <>Your os is <b>{os}</b></>;
}
```

This example is from [Mantine](https://mantine.dev/)'s documentation for this hook. So if you're viewing it from an android device, for example, you should get:

```xml
Your os is android
```

## 4️⃣ [createGlobalState](https://github.com/streamich/react-use/blob/master/src/factory/createGlobalState.ts)

`createGlobalSize` also belongs to [react-use](https://github.com/streamich/react-use) and it provides a way for you to create a global state that can be shared across multiple components without having to go for state management libraries like [redux](https://redux.js.org/).

In a typical React application, the state is usually managed locally within a component and passed down to child components through props. However, in some cases, it may be desirable to have a global state that can be accessed and updated by components that are not directly connected in the component tree. This hook serves that purpose quite well.

Under the hood, `createGlobalState` accepts some initial state (or not) of any type and returns a function which has the typical behaviour of `useState` in that it returns an array of the current state and a setter. This function can then be used in as many components as necessary.

```typescript
import { useState } from 'react';
import { IHookStateInitAction, IHookStateSetAction, resolveHookState } from '../misc/hookState';
import useEffectOnce from '../useEffectOnce';
import useIsomorphicLayoutEffect from '../useIsomorphicLayoutEffect';

export function createGlobalState<S = any>(
  initialState: IHookStateInitAction<S>
): () => [S, (state: IHookStateSetAction<S>) => void];
export function createGlobalState<S = undefined>(): () => [
  S,
  (state: IHookStateSetAction<S>) => void
];

export function createGlobalState<S>(initialState?: S) {
  const store: {
    state: S;
    setState: (state: IHookStateSetAction<S>) => void;
    setters: any[];
  } = {
    state: initialState instanceof Function ? initialState() : initialState,
    setState(nextState: IHookStateSetAction<S>) {
      store.state = resolveHookState(nextState, store.state);
      store.setters.forEach((setter) => setter(store.state));
    },
    setters: [],
  };

  return () => {
    const [globalState, stateSetter] = useState<S | undefined>(store.state);

    useEffectOnce(() => () => {
      store.setters = store.setters.filter((setter) => setter !== stateSetter);
    });

    useIsomorphicLayoutEffect(() => {
      if (!store.setters.includes(stateSetter)) {
        store.setters.push(stateSetter);
      }
    });

    return [globalState, store.setState];
  };
}

export default createGlobalState;
```

The `createGlobalState` custom hook also implements some other hooks in [react-use](https://github.com/streamich/react-use) like [useIsomorphicLayoutEffect](https://github.com/streamich/react-use/blob/master/src/useIsomorphicLayoutEffect.ts) and [useEffectOnce](https://github.com/streamich/react-use/blob/master/src/useEffectOnce.ts).

### Usage

```xml
const useGlobalValue = createGlobalState<number>(0);

const CompA: FC = () => {
  const [value, setValue] = useGlobalValue();

  return <button onClick={() => setValue(value + 1)}>+</button>;
};

const CompB: FC = () => {
  const [value, setValue] = useGlobalValue();

  return <button onClick={() => setValue(value - 1)}>-</button>;
};

const Demo: FC = () => {
  const [value] = useGlobalValue();
  return (
    <div>
      <p>{value}</p>
      <CompA />
      <CompB />
    </div>
  );
};
```

In the use case above `createGlobalState` was initialized with 1 and gets `number` passed as a type parameter. It then returns a useState-like function which is called `useGlobalValue` here.

`CompA` and `CompB` use the setter from it to update the global state variable `value`. This can be used everywhere in the application. I like to see `createGlobalState` as an umbrella state holder that shares data among components.

## 5️⃣[useWindowSize](https://github.com/streamich/react-use/blob/master/src/useWindowSize.ts)

One use case for the `useWindowSize` hook, which belongs to [react-use](https://github.com/streamich/react-use), is for responsive design. The `useWindowSize` hook keeps track of the size of the browser window which makes it possible to apply different styles (layouts, displays, etc.) to user interfaces at different sizes. It returns an object containing the current width and height of the window.

```typescript
import { useState } from 'react'

import { useEventListener, useIsomorphicLayoutEffect } from '..'

interface WindowSize {
  width: number
  height: number
}

function useWindowSize(): WindowSize {
  const [windowSize, setWindowSize] = useState<WindowSize>({
    width: 0,
    height: 0,
  })

  const handleSize = () => {
    setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight,
    })
  }

  useEventListener('resize', handleSize)

  // Set size at the first client-side load
  useIsomorphicLayoutEffect(() => {
    handleSize()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return windowSize
}

export default useWindowSize
```

The hook is typed with `WindowSize`. It gets the dimensions from the window object, listens for `resize` and gets updated based on that.

### Usage

```typescript
import {useWindowSize} from 'react-use';

const Demo = () => {
  const {width, height} = useWindowSize();

  return (
    <div>
      <div>width: {width}</div>
      <div>height: {height}</div>
    </div>
  );
};
```

The hook returns `windowSize` which is destructured in the example above (as is typical in handling return values from hooks in React) and the value can be easily used in the application.

## Final Thoughts

As you can see there are quite a lot of customs hooks out there that serve as many useful utility purposes as:

-   sensors
    
-   animations
    
-   state
    
-   UI
    
-   side-effects
    

I thought about writing these custom hooks by myself but there's no point reinventing the wheel and they're probably more reliable since many developers are working on the libraries.