Lately I’ve been making use of Custom React Hooks to clean up my codebase and it has been doing wonders for me! I’ve managed to move so much complex duplicate code out into reusable custom hooks.

In this article I’m going go through an example of how I used a custom hook to track my window browser window across multiple components in my app with a single line. I’m a huge advocator for using TypeScript, so of course I’ll be using TypeScript in my examples.

Before Implementing The Hook

For those who aren’t familiar with custom hooks, an easy way to grasp them is to identify the problem they solve. So this is how we’d implement our functionality without using a custom hook.

Let’s say we had a component and where we want to render a different component based off the width of the browser window.

For Example,

If the window width is 800px or larger, then we want to render the Desktop component.

or larger, then we want to render the component. If the window width is smaller than 800px , then we want to render the Mobile component.

Here’s a simple solution to that:

import React, { useEffect, useState } from 'react' const MyComponent: FC = () => {

const [width, setWidth] = useState(window.innerWidth)



const handleResize = () => setWidth(window.innerWidth) useEffect(() => {

window.addEventListener('resize', handleResize)

return () => window.removeEventListener('resize', handleResize)

}, []) return width < 800 ? <Mobile /> : <Desktop />

}

Let’s break this code down, shall we?

In this component, we are storing the window width in a a width variable stored by React's setState variable.

This setState width value gets set via the setWidth callback which is called by the handleResize function.

We use useEffect to add an event listener to the window that calls handleResize whenever the window is resized. This event listener is added when the component gets mounted. When the component is unmounted, we clean things up by removing the event listener.

Finally we perform a simple ternary statement to decide which component to render based off the width value that is always updated upon a window resize.

It’s a pretty basic example, but that’s done so that we can focus on what’s important and filter out irrelevant information.

What’s Wrong With This Approach?

Nothing…

Yeah, nothing is wrong with it if you’re just going to use it in just this one place. However, if you want to use this same thing somewhere else in your project you’ll have the following 2 issues:

You need to write those same long 6 lines of setup code in every component you want to use this feature.

You’ll be setting up a new event listener for every component you use this method on.

Wouldn’t it be nice if we had some easy to use reusable solution that solves these problems?

Well, thanks to some of React’s handy features we can come up with a solution that only has one line of setup per component and only adds one event listener that gets used by all the component.

The Solution

I’m going to spoil things and show you the simple implementation first just so that you have some context of what we need to build to accomplish our goal.

After coding the solution we’ll have a useViewport hook that we've developed in another file that will work as follows:

import React, { FC } from 'react'

import { useViewport } from './use-viewport' const MyComponent: FC = () => {

// See everything simplified to this one line.

const { width } = useViewport() return width < 800 ? <Mobile /> : <Desktop />

}

So much simpler right? Now let’s go create our useViewport hook and get it implemented!

To prevent multiple events listeners from being subscribed, we’re going to make use of React’s Context API.

Here’s the full implementation of the use-viewport file:

import React, {

createContext,

FC,

useContext,

useEffect,

useState,

} from 'react' interface IViewport {

width: number

} const ViewportContext = createContext<IViewport>({

width: window.innerWidth,

}) export const ViewportProvider: FC = ({ children }) => {

const [width, setWidth] = useState(window.innerWidth) const handleResize = () => setWidth(window.innerWidth) useEffect(() => {

window.addEventListener('resize', handleResize)

return () => window.removeEventListener('resize', handleResize)

}, []) return (

<ViewportContext.Provider value={{ width }}>

{children}

</ViewportContext.Provider>

)

} export function useViewport() {

return useContext<IViewport>(ViewportContext)

}

Ok, so in here quite a lot is happening. What we’re essentially doing is we are going to wrap our entire app in this ViewportProvider so that the event listener subscription only happens once.

Then we create the custom useViewport hook to easily access the width value within our components. If you're not familiar with React's Context API, then I highly recommend reading up on React's official docs to better grasp it. Although personally, I learnt most about the Context API through using them while building custom hooks.

While reading the code above, the most important parts to take away are the following:

ViewportContext is created using React's createContext function.

is created using React's function. ViewportContext only holds the value width so that it can be used by the useViewport hook that we've create. Everything else isn't important for our hook implementation.

only holds the value so that it can be used by the hook that we've create. Everything else isn't important for our hook implementation. The width value is updated behind the scenes in the ViewportProvider component.

Final Steps

This part is very important! Our useViewport hook can't be used just yet. We need to implement the ViewportProvider component in our app so that the implementation of hook actually has access to the context. Remember: React context - just like Redux - needs a provider.

To do this, I recommend implementing this near the root of your React App .

Something like this should suffice:

import React from 'react'

import ReactDOM from 'react-dom' import App from './app'

import ViewportProvider from './use-viewport' ReactDOM.render(

<ViewportProvider>

<App />

</ViewportProvider>,

document.getElementById('root')

)

And that’s it! You should be able to make use of your useViewport hook across your app with ease now 🙂

Hope you enjoyed the read and that this helps you in your projects, big and small.