preactjs / preact-devtools

Browser extension for inspection Preact applications

Home Page:https://preactjs.github.io/preact-devtools/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DX: Allow providing display names for various hooks to distinguish them more easily

keichinger opened this issue · comments

We've briefly talked about this on the Slack channel where I was asking whether it's possible to provide display names to various hooks, so my useState(), useRef(), useCallback() (etc.) hooks provide me with more context than it is right now:

image

To know what useState() entry is referring to the one I'm most interested in, I'd have to open the component inside my IDE and check the order. This becomes unnecessarily complicated and even prone to errors as soon as I'm debugging multiple components.

Being able to give them names (that do persist throughout reloads) would be awesome!

I wonder if this could be inferred from the callsite? We could show the state variable name for useState/useReducer/useRef.

I implemented two different approaches as Babel transforms that infer hook names from their assigned variables:

1: wrap each hook callsite in a name:

https://astexplorer.net/#/gist/a42d590e748e054d566a8a8832b71dda/d07aad878acc4413243c464943ecc481b6200d13

// in:
  const [text, setText] = useState('hello');
// out:
  const [text, setText] = Object.assign(useState('hello'), { _name: "text" });

2: inject a special useRef() into functions that provides an Array of state hook names:

https://astexplorer.net/#/gist/775bddf0bb9ed010ab023b127d090291/dc36d52ff11202dcc5fa140b84d71cd1fc4b6ba7

// in:
  const [text, setText] = useState('hello');
  const foo = useRef();
// out:
  useRef()._hookNames = ["text", "counter", "rootElement"];
  const [text, setText] = useState('hello');
  const foo = useRef();

@developit I dabbled around with that a bit, but it's easier for devtools if it is implemented similar to useDebugValue. That way we don't need to add unknown properties to arrays and it's more clear what the wrapped function does imo:

const [text, setText] = useDebugName(useState('hello'), 'hello-state');
const foo = useDebugName(useRef(), 'foo');

@marvinhagemeister the output seems the same as the first output in terms of readability:

const _hookName = (v, n) => (v._name = n, v);

function Foo() {
  const [text] = _hookName(useState('hello'), "text");

If the properties are weird we could technically also have the debug hook define .toString() - that would be a standard property. It could also import the helper as useDebugName() exported from preact/debug.

I guess I'm a little apprehensive of landing a hook in core for this. I might be over thinking it though, maybe it would get Dead Code Eliminated in prod builds...

@developit Right, I didn't see the mismatch between the code sample under 1) vs the linked snippet in AST-Explorer, my bad. For devtools I'd prefer to call a listener on options rather than assigning a property to the return value of the hook. Doing so needs results in more changes needed for the devtools to access them and filter them out.

I'm worried that we may be deopted for assigning custom properties to an array. Maybe my worries are overblown though as I don't have hard numbers to back this up.

Thinking a bit more about it, I'm agreeing with your suggestion of making it a dev only thing and not a hook. It's not something users should use themselves and it isn't a "true" hook per se.

@developit FYI: Released the babel plugin over at https://github.com/preactjs/babel-plugin-transform-hook-names in our org.

Only step missing is cutting a new devtools release.

Awesome work! I'm amazed by how fast everything went from the very first discussion on Slack 🎉 🎉 🎉