Prevent "window is not defined" Errors With a useClientSide() Custom Hook
Tags: nextjs, react, javascript, beginners
Published: 2021-09-27 11:09:41

**TLDR:**

- There is no `window` object on the server - trying to access the `window` object will thrown an error in server side rendered code, and in Node.js based development environments
- You can access `window` in a `useEffect` hook, as `uesEffect` only runs on the client
- We want to avoid having to repeat this `useEffect` logic in every component that needs to access `window`
- Instead we can move this logic into a custom react hook to keep everything super tidy! 🎉

The finished `useClientSide()` hook:
```jsx
const useClientSide = func => {
  const [value, setValue] = useState(null)
  useEffect(() => {
    setValue(func())
  }, [func])
  return value
}

const getUserAgent = () => window.navigator.userAgent

export default function Example() {
  const userAgent = useClientSide(getUserAgent)
  return <>{userAgent && <p>{userAgent}</p>}</>
}
```
Heres a [stackblitz⚡ Next.js demo](https://stackblitz.com/edit/useclientside-hook?file=pages%2Findex.js). 

## The Problem 

When trying to access window with react frameworks like Next.js you might run into issues when trying to access the window object and see the following error: 

![window is not defined error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dawxjxnxxv94b6rqex6m.png)

This is because somewhere in your app window is trying to be accessed from the server, where it does not exist. 

In Next.js this could be because we're trying to access `window` on a page that uses [getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering), which makes the page a server side rendered (SSR).

You might think:
> "But my app is static, with no server side rendering, why am I getting Server errors??"

Most development environments are created by running a local Node.js server (Next.js does this). And as Node.js runs on the server, there is no `window` object

## Example Problem: Device Detection

Say if you had a button, and on a touch device you want it to say **"Tap here"**, otherwise it would say **"Click here"**, you could check the `window` object for `navigator.userAgent`. 

This would tell us what device type they're on, like Android or IOS, and we could infer if it's a touch device. There are other ways to check touch devices, but for the purposes of this tutorial, we'll do it this way.

You could approach it like this for client side rendered apps:

```jsx
const isTouchDevice = () => {
  const ua = window.navigator.userAgent
  if (ua.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|BB10|PlayBook|IEMobile|Opera Mini/i)) {
    return true
  }
  return false
}

export default function Example() {
  const isTouch = isTouchDevice()
  return <button>{isTouch ? 'Tap here!' : 'Click here!'}</button>
}
```
Note: I won't show the code for `isTouchDevice()` again, just to keep the code examples clearer. Just remember it returns `true` or `false`! :)

Here we are getting the `window.navigator.userAgent` and then passing it into our function and checking if it contains any identifiers for touch devices, if it does return `true`, otherwise return `false`. 

However, this code will cause the `window is not defined` error, as our local dev environment is running on a server, where there is no window object! 

## A Common, But Not Ideal Solution 🙅‍♂️

We could check if window is not defined by adding this line at the top of any function that tries to access window:
```jsx
  if (typeof window === 'undefined') return 
```
Note you cannot do `window === undefined` as this assume would `window` is declared, but has no value. When actually, `window` hasn't been declared at all. This is the difference between:
* `undefined`: a variable that is declared but not initialised or defined (aka not given a value)
* `not defined`: a variable that has not been declared at all 

Using `typeof window === 'undefined'` is far from ideal and can cause rendering issues as explained in this brilliant in-depth article by @joshwcomeau: [The Perils Of Rehydration](https://www.joshwcomeau.com/react/the-perils-of-rehydration/).

## The Solution: Only Reference Window On The Client 👍

We can do this by running our `isTouchDevice()` function inside a `useEffect`, which only runs on the client when the component mounts.

We can also store the return value of `isTouchDevice()` in state by using `useState`. Storing it in state means that it's value is preserved during re-renders.

Here's a working example:
```jsx
import { useEffect, useState } from 'react'

const isTouchDevice = () => {} // returns true or false, see code above

export default function Example() {
  const [isTouch, setisTouch] = useState(null)

  useEffect(() => {
    setisTouch(isTouchDevice())
  }, [])

  return <button>{isTouch ? 'Tap here!' : 'Click here!'}</button>
}
```

Once the component mounts (which only happens on the client) the function is run and the state of `isTouch` is updated to a `true` or `false` value, which causes our button to show the correct messaging.

🤔 But having to do this every time you want to use the `isTouchDevice` function is really a hassle and will lead to lots of needless repetition of `useEffect()`.

What would be much neater is a custom react hook that obfuscates all of this logic, allowing us to do something like this:
```jsx
export default function Example() {
  const isTouch = useIsTouchDevice()
  return <p>{isTouch ? 'Tap here!' : 'Click here!'}</p>
}
```

That would help make things easier, but something else would be better...

## A Step Further: Making a `useClientSide()` Hook! 🔥

What would be even better than a `useIsTouchDevice()` hook? A flexible, generalized custom hook that could take any function as an argument, and only run that function on the client side: a `useClientSide()` hook! 😃

Example:
```jsx
const useClientSide = func => {
  const [value, setValue] = useState(null)
  useEffect(() => {
    setValue(func())
  }, [func])
  return value
}

const getUserAgent = () => window.navigator.userAgent

export default function Example() {
  const userAgent = useClientSide(getUserAgent)
  return <>{userAgent && <p>{userAgent}</p>}</>
}
```

What this custom hook is doing:
* taking a function as an argument
* calling that function in a `useEffect` hook (which is only done on the client)
* saving what is returned by that function to the local state of the `useClientSide()` hook
* then returning that local state value 

Now let's use it with our `isTouchDevice()` function:
```jsx
import { useEffect, useState } from 'react'

const isTouchDevice = () => {
  const ua = window.navigator.userAgent
  if (ua.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|BB10|PlayBook|IEMobile|Opera Mini/i)) {
    return true
  }
  return false
}

const useClientSide = func => {
  const [value, setValue] = useState(null)
  useEffect(() => {
    setValue(func())
  }, [func])
  return value
}

export default function Example() {
  const isTouch = useClientSide(isTouchDevice)
  return <p>{isTouch ? 'Tap here!' : 'Click here!'}</p>
}
```
Heres a [stackblitz⚡ Next.js demo](https://stackblitz.com/edit/useclientside-hook?file=pages%2Findex.js).  

If you want to check the `isTouch` is working as expected, just simulate a mobile device using your browser's dev tools. Like [device mode](https://developer.chrome.com/docs/devtools/device-mode/) in chrome.


## Done!

There we go! All working! We have a useful, reusable custom hook that allows use to run any client specific code easily! 😃 🎉

I built this hook while building [episoderatings.com](http://episoderatings.com) (a way to view episode ratings in a graph), to help me easily detect touch devices and display specific messaging! 

![episode ratings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pmu59cj5jrtxau33zu3z.png)

If you like React, Next.js and front end development, feel free to follow me and say hi on [twitter.com/_AshConnolly](http://twitter.com/_AshConnolly)! 👋 🙂

Stunning cover photo by [Spencer Watson on Unsplash!](https://unsplash.com/photos/dEOC8M_lmxI)