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.
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 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).
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.
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 hookfunction 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 hookfunction 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 2.
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(...)).
3.
At least, this is how I usually write them.
The thing is that this way, memo
d 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 4.
There are a few different ways to solve this. You can either:
- 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) { ... })
- 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);
- 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)
- 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 settingdisplayName
.
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 displayName
s 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.
-
↩
The
React docs: React.Component `displayName`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: forwardRef - Parametersrender
: The render function for your component. React calls this function with the props andref
that your component received from its parent. The JSX you return will be the output of your component. -
↩
GitHub: React forwardRef source codeforwardRef
requires a render function but received amemo
+ component. Instead offorwardRef(memo(...))
, usememo(forwardRef(...)).
-
↩
ForwardRef: Displaying a custom name in DevToolsReact.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 -
↩
GitHub @types/react NamedExoticComponenttypescript interface NamedExoticComponent<P = {}> extends ExoticComponent<P> { displayName?: string | undefined; }
-
↩
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
Thoughtspile - What is a react component, anyways?React.memo
,forwardRef
,Context
and some other builtins [React.lazy
]. They are called exotic components and trigger special renderer behavior.