Skip to content

Big O Visualizer

Added debug modal

Change2 min read

I've added a debug modal so I can experiment with some new features on the production version of this application. 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.

...

...

...

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

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 which is a powerful and expressive new way to reuse functionality between components that was introduced with React 16.8.

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. I adopted the useLocalStorage recipe from Gabe Ragland's excellent usehooks.com website 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:

src/settings.ts
1import { Dispatch, useState } from "react"
2
3function useLocalStorageBackedEnum<TEnum extends string, TEnumValue extends number | string>(
4 key: string,
5 enumType: { [key in TEnum]: TEnumValue },
6 defaultValue: TEnumValue
7): [TEnumValue, Dispatch<TEnumValue>] {
8 const [storedValue, setStoredValue] = useState(() => {
9 try {
10 const item = window.localStorage.getItem(key)
11 if (item === null) return defaultValue
12 return (Number.isNaN(Number(item)) ? item : +item) as TEnumValue
13 } catch (error) {
14 console.log(error)
15 return defaultValue
16 }
17 })
18
19 const setValue = (value: TEnumValue) => {
20 try {
21 setStoredValue(value)
22 window.localStorage.setItem(key, value.toString())
23 } catch (error) {
24 console.log(error)
25 }
26 }
27
28 return [storedValue, setValue]
29}

This custom Hook makes introducing settings as easy as:

src/settings.ts
1export enum PreanalyzedMode {
2 Enabled = "enabled",
3 Disabled = "disabled",
4 Persist = "persist",
5}
6
7export enum WebWorkerMode {
8 Enabled = 0,
9 Disabled = 99999999999999,
10 XLOnly = 1000000,
11}
12
13export enum StopwatchMode {
14 None = "none",
15 Analyzer = "analyzer",
16 Algorithm = "algorithm",
17}
18
19export const usePreanalyzedMode = () =>
20 useLocalStorageBackedEnum("preanalyzed-mode", PreanalyzedMode, PreanalyzedMode.Enabled)
21export const useWebWorkerMode = () =>
22 useLocalStorageBackedEnum("web-worker-mode", WebWorkerMode, WebWorkerMode.Disabled)
23export const useStopwatchMode = () =>
24 useLocalStorageBackedEnum("stopwatch-mode", StopwatchMode, StopwatchMode.None)

I ❤ React