Avoid anonymous components with `displayName`

Published

The React dev tools are great and you should use them, they’ll make your life as a React dev easier, trust me. The components tab of the React devtools displays the tree of components (with their names). You can search for a component and view the values of its props and state, even other hooks too. For me, the React dev tools have largely supplanted the need to console.log in components. Though there’s nothing wrong with that, or the debugger if that’s more your thing.

Anyway, there are two exceptions where the devtools are unable to infer a name of a component. Contexts and ’wrapped’ or ’higher-order’ components, for example components wrapped in memo and forwardRef. The devtools need some help to be able to display a name for those, this is where the displayName property comes in 1.

By default, the devtools will show Context.Provider for every context. Context objects have a displayName property that you can set to a helpful name and React will use it in debugging messages. The React DevTools will also use it to display a name (big surprise),

It makes it easier to see what’s what in the React dev tools when dealing with multiple nested context providers. I certainly recommend setting it for contexts in library code to . I set this for each and every Context object I create, I should really make an ESLint rule for it 🤔.

Screenshot of React Devtools showing multiple Context.Providers without displayName setScreenshot of React Devtools showing multiple Context.Providers with displayName set

Pyramids of Context.Providers are a common sight in React, whether you should be making these is another question. Consistently setting Context.displayName at least makes it easy to see which is which.

Context.displayName for a useSafeContext hook

It’s to use a custom provider and consumer hook instead of useContext() directly. For one, a custom consumer hook is named (like useSomething) which clearly emphasizes its intent. And second, it can throw an error with a helpful warning when it’s used outside the provider. Just read this post by Kent C Dodds: How to use React Context effectively. Or this one Why I always wrap Context.Provider and useContext by Vladimir Klepov.

One practical application of Context.displayName is making a generic consumer hook for contexts. To build on Kent’s example:

const CountContext = React.createContext();
CountContext.displayName = "CountContext";

// custom consumer hook
function useSafeContext(contextObject) {
  const context = React.useContext(contextObject);

  if (!contextObject.displayName) {
    throw new Error(
      "Context.displayName is not set, it must be set for useSafeContext"
    );
  }

  if (context === undefined) {
    // Get the displayName without "Context", so just "Count" here
    const contextName = contextObject.displayName.split("Context")[0];
    throw new TypeError(`use${contextName} must be used within a ${contextName}Provider`);
    // "useCount must be used within a CountProvider"
  }

  return context;
}

// named hook
function useCount() {
  const context = useSafeContext(CountContext);

  return context;
}

React.memo() and React.forwardRef() are both utilities that wrap components. In case you haven’t heard of them, I’ll leave it to the docs to explain what they do exactly.

Components wrapped in memo() or forwardRef() are commonly written as inline anonymous functions, like this:

const HeavyComponent = React.memo((props) => (
  <div>{/* Big expensive component tree here */}</div>
));

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

At least, this is how I write them. The thing is that this way memoed components will appear as Anonymous memo in the React dev tools because React can’t infer a name from them, which makes them hard to find. For function components, React uses Function.name (I’m pretty sure) to get a display name. Anonymous functions, by definition, lack a name, so React resorts to the "Anonymous" fallback.

Likewise, forwardRef’d components written like this will show as Anonymous with a ForwardRef badge, which is equally unhelpful 2.

There are a few different ways to solve this. You can either:

  1. Use a named function declaration with the same name for the wrapped function. This can lead to shadowing, which you or your ESlint config may not like.
const HeavyComponent = React.memo(
  function HeavyComponent(props) { ... }
)
  1. Reassign the function. Same story, some folks think it looks weird and there’s a rule in the default recommend ESLint config that complains about it.
function HeavyComponent(props) {
  ...
}
HeavyComponent = React.memo(HeavyComponent)
  1. Give the unmemoized component a different name. The devtools will display the name of the unmemoized component, in this case UnmemoizedHeavyComponent memo, which doesn’t feel right to me.
function UnmemoizedHeavyComponent(props)  { ... }

const HeavyComponent = React.memo(UnmemoizedHeavyComponent)

  1. All this hassle is exactly why memoized and forwardReffed components also have a displayName property. We get to have it all by writing an anonymous function and setting displayName.
const HeavyComponent = React.memo(() => (props)  { ... })
HeavyComponent.displayName = "HeavyComponent"

Keep it in mind and help yourself and your fellow devs next time writing Context, memoized or forwardReffed components!

eslint-pugin-react/display-name

There’s a somewhat dated eslint-plugin-react rule to ensure each component has a displayName. Just from the examples on the rules page you can tell it’s dated, createReactClass is from before my time. It does show the case for memoed component but I don’t think I’ve ever turned this rule on.

displayName is also a property on class components, it’s not that useful as React infers it from the class name, it’s only for overriding the inferred name. On top of that I haven’t written a class component in a long time.

  1. The displayName string is used in debugging messages. Usually, you don’t need to set it explicitly because it’s inferred from the name of the function or class that defines the component. You might want to set it explicitly if you want to display a different name for debugging purposes or when you create a higher-order component React docs: React.Component `displayName`

  2. React.forwardRef accepts a render function. React DevTools uses this function to determine what to display for the ref forwarding component. For example, the following component will appear as ”ForwardRef” in the DevTools ForwardRef: Displaying a custom name in DevTools

  3. typescript interface NamedExoticComponent<P = {}> extends ExoticComponent<P> { displayName?: string | undefined; } @types/react NamedExoticComponent

  4. For the most boring take, let’s see what React itself considers a component, by looking at @types/react and the react-dom implementation. Actually, many things work: [...] An object returned from React.memo, forwardRef, Context and some other builtins [React.lazy]. They are called exotic components and trigger special renderer behavior. Thoughtspile - What is a react component, anyways?