Debugging production with hidden modals
Tags: devops, react, typescript, gatsby
Published: 2020-05-14 04:29:09


I've added a debug modal so I can experiment with some new features on the production version of the [Big O Visualizer](https://omi.cr/). This is especially useful on my iPhone where my debugging tools are limited. So where is this screen? Well, I hid it, because it's super secret and I don't want visitors to mess with these experimental options.

...

![...](https://dev-to-uploads.s3.amazonaws.com/i/8h50iobdjr4q6xd5hyi8.gif)

...

Ok, I'll tell you where it is. There's an invisible button to the right of the website's title at the top of the screen. If you click/tap it rapidly (about eight times in less than three seconds) the modal will appear.

# You should totally do that!

If you think this is just some crazy feature I've implemented in my own pet-project, think again because I've been building these hidden debug tools into most of my former client's applications (and yes with their consent). Why? Because I'm a huge fan of *Testing In Production* (yay) and not-such-a-big-fan of *But It Worked On Develop/Test/Acceptance/Demo/RC/Spike/Bla* (boo).

 The problem is that we tend to avoid building these tools into our products because they're of no value to our end-users. The fallacy is that the teams who build these products do not see themselves (or are not seen as) one of the most valuable end-users! So we limit these debug tools to non-production environments (or not build them at all), where they're of little value. Such a missed opportunity! So build debug windows and ship them proudly, like YouTube's video player:

![Stats for nerds](https://dev-to-uploads.s3.amazonaws.com/i/2lcerh10c2vhmboiozmd.png)

> Disclaimer: the options of these debug tools should be limited for (obvious) security reasons. Don't try to ship a hidden "Bypass Login Screen" or "Dump Database" option, because some hacker will definitely find it.

# Custom React Hooks

All the settings are implemented as [React Hooks](https://reactjs.org/docs/hooks-intro.html) which is a powerful and expressive new way to reuse functionality between components that was introduced with [React 16.8](https://reactjs.org/blog/2019/02/06/react-v16.8.0.html).

Thanks to React Hooks, any component can use a setting with a simple one-liner: `const [preanalyzedMode] = usePreanalyzedMode()`, and then use the setting inside its `useEffect` method. Any changes to the setting will automagically propagate to the components, so they can update their state accordingly.

Each setting is a TypeScript `enum` that is persisted to the browser's [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). I adopted the `useLocalStorage` recipe from [Gabe Ragland's excellent usehooks.com website](https://usehooks.com/useLocalStorage/) and rolled into my own more TypeScripty `useLocalStorageBackedEnum`. This hook factory takes the name of the storage key, the `enum` used for the setting and a default value for the setting and returns a fully functional React Hook. The result looks like this:

```typescript
import { Dispatch, useState } from "react"

function useLocalStorageBackedEnum<TEnum extends string, TEnumValue extends number | string>(
  key: string,
  enumType: { [key in TEnum]: TEnumValue },
  defaultValue: TEnumValue
): [TEnumValue, Dispatch<TEnumValue>] {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key)
      if (item === null) return defaultValue
      return (Number.isNaN(Number(item)) ? item : +item) as TEnumValue
    } catch (error) {
      console.log(error)
      return defaultValue
    }
  })

  const setValue = (value: TEnumValue) => {
    try {
      setStoredValue(value)
      window.localStorage.setItem(key, value.toString())
    } catch (error) {
      console.log(error)
    }
  }

  return [storedValue, setValue]
}
```

This custom Hook makes introducing settings as easy as:

```typescript
export enum PreanalyzedMode {
  Enabled = "enabled",
  Disabled = "disabled",
  Persist = "persist",
}

export enum WebWorkerMode {
  Enabled = 0,
  Disabled = 99999999999999,
  XLOnly = 1000000,
}

export enum StopwatchMode {
  None = "none",
  Analyzer = "analyzer",
  Algorithm = "algorithm",
}

export const usePreanalyzedMode = () =>
  useLocalStorageBackedEnum("preanalyzed-mode", PreanalyzedMode, PreanalyzedMode.Enabled)
export const useWebWorkerMode = () =>
  useLocalStorageBackedEnum("web-worker-mode", WebWorkerMode, WebWorkerMode.Disabled)
export const useStopwatchMode = () =>
  useLocalStorageBackedEnum("stopwatch-mode", StopwatchMode, StopwatchMode.None)
```

I ❤ React