Avoid Anonymous Components With the displayName Property

There are a few cases where the React devtools can’t infer a name for a component, use the displayName property to avoid anonymous components in the devtools.

Published · Last updated

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 parent 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 .

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).

This 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 slightly improve DX.

I set this for each and every Context object I create, I should really make an ESLint rule for it 🤔. Update: I did! It’s now an option called checkContextObjects in the eslint-plugin-react displayName rule. It’s disabled by default so go ahead and add it your config.

Screenshot of React Devtools showing multiple Context.Providers without the displayName property setScreenshot of React Devtools showing multiple Context.Providers with displayName property 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 considered good practice to use a custom provider and consumer hook for contexts instead of useContext() directly. For one, a custom consumer hook is named (like useSomething) 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 Error(`use${contextName} must be used within a ${CountContext.displayName}Provider`);
// "useCount must be used within a CountContextProvider"
}
return context;
}
// named custom 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>
));

Using memo and forwardRef together

The JSX-returning function passed to forwardRef isn’t technically a component but a render function . This implies you cannot pass memo components to forwardRef.

If you need both memo and forwardRef around a component, you need apply forwardRef first and then memoize it: memo(forwardRef(...)). .

At least, this is how I usually write them. The thing is that this way, memod 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 .

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

  1. Use a named function declaration with the same name the variable of the memoized component. This leads 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, or memoized and forwardReffed components!

There’s an ESLint rule in eslint-plugin-react to ensure each component has a displayName and it can catch missing displayNames on memo and forwardRef components. With the checkContextObjects option enabled, it can detect anonymous contexts too. I recommend you to enable it so you won’t forget consistently adding display names.

  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. render: The render function for your component. React calls this function with the props and ref that your component received from its parent. The JSX you return will be the output of your component.
    React Docs: forwardRef - Parameters
  3. forwardRef requires a render function but received a memo + component. Instead of forwardRef(memo(...)), use memo(forwardRef(...)).
    GitHub: React forwardRef source code
  4. 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
  5. typescript interface NamedExoticComponent<P = {}> extends ExoticComponent<P> { displayName?: string | undefined; }
    GitHub @types/react NamedExoticComponent
  6. 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?