The power of custom hooks in React (responsive design example)
Tags: react, javascript, webdev, beginners
Published: 2021-08-24 20:44:17

Generally, custom hook is a great pattern in order to handle modularity and composition in your app. you can write a custom hook for almost everything!

BTW, if you're interested in custom hooks, I assume you are familiar with React hooks concept. if not, no worries, you can read about it [here](https://reactjs.org/docs/hooks-overview.html).

Something that worth to mention here (taken from React docs):

> *Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don’t work inside classes — they let you use React without classes. (We don’t recommend rewriting your existing components overnight but you can start using Hooks in the new ones if you’d like.)*

!["Captain hook"](https://media.giphy.com/media/B4ZgcoPTHYXL2/giphy.gif)

Let's take responsive window handling as an example..

The most common approach for responsive design is **CSS media queries**, but in some cases, we will want to handle it via **JavaScript** (conditionally render components, execute some logic when window size is changing to some point, etc.)


In the example below you will see how we can use **React hooks** for that purpose + compose code and reuse/share it across an app(s).

### Some declarations

Our custom hook is called `useResponsiveWindow` and gets `sizes` as an optional object.

> *Hook name convention is a `use` prefix followed by a story telling, camel cased name (like `useState`, `useRef`, `useContext`, etc.)*


Most of the apps use these common sizes which declared as a default using `DEFAULT_SIZES`, but feel free to change or pass your own to the hook.

`DESKTOP_MIN` size is also a standard minimum resolution for a desktop view (Again, not a taboo..). we will use it later.

First, we will want to save state for first, on load, width & height using `useState`.
```
const DEFAULT_SIZES = {
  small: [1366, 768],
  medium: [1400, 900],
  large: [1920, 1080],
  mobile: [360, 640]
};

export enum ResolutionState {
  XS = "Extra Small",
  SMALL = "Small",
  MEDIUM = "Medium",
  LARGE = "Large"
}

const DESKTOP_MIN = [1280, 720];

const useResponsiveWindow = (sizes = DEFAULT_SIZES) => {
  const [width, setWidth] = useState(window.innerWidth);
  const [height, setHeight] = useState(window.innerHeight);
  const resizeTimer = useRef(null);

.........
```


### Track window size and store it


Adding resize event listener (remove it on unmount) and execute `handleWindowResize` which will save the new values.
```
.....

  const handleWindowResize = useCallback((e) => {
    clearTimeout(resizeTimer.current);
    resizeTimer.current = setTimeout(() => {
      setWidth(e.target.innerWidth);
      setHeight(e.target.innerHeight);
              
    }, 200);

  }, [setWidth, setHeight, resizeTimer]);

  useEffect(() => {
    window.addEventListener('resize',handleWindowResize);
    return () => {
      window.removeEventListener('resize', handleWindowResize);
    };
  }, [handleWindowResize]);

.....
```


### Useful insights

Now that we have width, height and resolution thresholds, we get some insights that we can use in our application.

```
.....

  const resolutionState = useCallback((type) => {
    const index = type === 'width' ? 0 : 1;
    const value = type === 'width' ? width : height;
    if(value >= sizes?.small[index] && value < sizes?.medium[index]) {
      return ResolutionState.SMALL;
    } else if(value >= sizes?.medium[index] && value < sizes?.large[index]) {
      return ResolutionState.MEDIUM;
    } else if(value >= sizes?.large[index]) {
      return ResolutionState.LARGE;
    } else {
      return ResolutionState.XS;
    }
  }, [width, height]);

  const widthState = resolutionState('width');
  const heightState = resolutionState('height');

  const isMobile = useMemo(() => sizes?.mobile && width <= sizes?.mobile[0] && height <= sizes?.mobile[1], [width, height]);

  const isDesktop = useMemo(() => width >= DESKTOP_MIN[0] && height >= DESKTOP_MIN[1], [width, height]);

.....
```


### Consuming the hook



```
const SomeComponent= () => {
  const {
    width,
    height,
    isMobile,
    isDesktop,
    widthState,
    heightState
  } = useResponsiveWindow();

  useEffect(() => {
    console.log(`Width state now is: ${widthState}`);
    // do something here...
  }, [widthState]);

  return (
    <div>
      <p>{`${width} (${widthState}) x ${height} (${heightState})`}</p>
      {isMobile && <div>Mobile View</div>}
      {isDesktop && <div>Desktop View</div>}
    </div>
  );
};
```

You can view an example [here](https://react-ts-8gfbuo.stackblitz.io) and the source code here:

{% stackblitz react-ts-8gfbuo %}


### Conclusion


There are many libraries providing many custom hooks as a solution, and probably your app has a lot of those, but try to find some that you can implement yourself, it will:

* saves you bundle size.
* gives you a full control of your code.
* extend your code skills outside app borders, into the infra zone.

About this hook - it's  not perfect, and probably able to offer much more insights and useful data, according to your needs.

Suggestions and thought are more than welcome :)

Image by <a href="https://pixabay.com/users/terimakasih0-624267/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=2309593">Dean Moriarty</a> from <a href="https://pixabay.com/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=2309593">Pixabay</a>