2

I made a custom hook that registers data into a store keyed by the components that use it, and then removes the data from the store when the components unmount. Basics are like this

const [id] = useState(Math.random());

const setData = useCallback((data) => {
  if(data) {
    setRegistrations({
      ...registrations,
      [id]: data
    });
  } else {
    const {[id]: _, ...nextRegistrations} = registrations;
    setRegistrations(nextRegistrations);
  }
}, [id]);

useEffect(() => {
  // update the registration if the data changed
  setData(data); 
}, [data, setData]);

useEffect(() => {
  // remove the registration on unmount
  setData();
}, []);

I'm using Math.random() to generate a unique id for each component that uses my hook, and that works fine, but I was hoping there's a way to figure out the name of the component that is using my hook. It'd make debugging my application state easier if I could see which data was registered by which component, but instead all I have to look at is a random decimal.

Right now my store looks like this

{
  '0.5694216823063629': { ... }
  '0.002760497537984463': { ... }
}

I'd prefer if I could do something like this

{
  'MyComponent-1': { ... }
  'MyComponent-2': { ... }
  'MyOtherComponent-1': { ... }
}

Yes, I know I could just make components pass in a key, but that's missing the point. I want the API to remain pure and not pollute it just so debugging is easier.

Is there a way to figure out which component used my hook?

Justin Toman
  • 237
  • 1
  • 8
  • Components support a `displayName` property, but chances are that not every component in your all will have this set. – Linda Paiste Mar 20 '21 at 04:51

1 Answers1

4

Functions and the arguments object have deprecated properties that allow them to see who called them - Function.caller and arguments.callee. However, both of them are blocked in strict mode.

You can get the caller's name from the stack when throwing an error, but this is ugly, and you should only use it in development mode. This would only work for function component's, but since you're using it in a hook, it won't be a problem.

Example (taken from this article):

// taken from https://devimalplanet.com/javascript-how-to-get-the-caller-parent-functions-name
function whoIsMyDaddy() {
  try {
    throw new Error();
  } catch (e) {
    // matches this function, the caller and the parent
    const allMatches = e.stack.match(/(\w+)@|at (\w+) \(/g);
    // match parent function name
    const parentMatches = allMatches[2].match(/(\w+)@|at (\w+) \(/);
    // return only name
    return parentMatches[1] || parentMatches[2];
  }
}

const useDemo = () => {
  const daddy = whoIsMyDaddy()
  React.useEffect(() => console.log(daddy))
}

const ComponentX = () => {
  useDemo()
  
  return 'X'
}

const ComponentY = () => {
  useDemo()
  
  return 'Y'
}

ReactDOM.render(
  <div>
    <ComponentX />
    <ComponentY />
  </div>,
  root
)
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

Note: using Math.random() as an id is not a good idea, since Math.random() is not that random - see this answer. You can use Crypto.getRandomValues() if supported, or an external uuid library.

Ori Drori
  • 183,571
  • 29
  • 224
  • 209