Skip to content

Composition

Material UI tries to make composition as easy as possible.

Wrapping components

To provide maximum flexibility and performance, Material UI needs a way to know the nature of the child elements a component receives. To solve this problem, we tag some of the components with a muiName static property when needed.

You may, however, need to wrap a component in order to enhance it, which can conflict with the muiName solution. If you wrap a component, verify if that component has this static property set.

If you encounter this issue, you need to use the same tag for your wrapping component that is used with the wrapped component. In addition, you should forward the props, as the parent component may need to control the wrapped components props.

Let's see an example:

<span class="token keyword">const</span> <span class="token function-variable function">WrappedIcon</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Icon</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>
WrappedIcon<span class="token punctuation">.</span>muiName <span class="token operator">=</span> Icon<span class="token punctuation">.</span>muiName<span class="token punctuation">;</span>
Press Enter to start editing

Component prop

Material UI allows you to change the root element that will be rendered via a prop called component.

How does it work?

The custom component will be rendered by Material UI like this:

<span class="token keyword">return</span> React<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>component<span class="token punctuation">,</span> props<span class="token punctuation">)</span><span class="token punctuation">;</span>

For example, by default a List component will render a <ul> element. This can be changed by passing a React component to the component prop. The following example will render the List component with a <nav> element as root element instead:

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">List</span></span> <span class="token attr-name">component</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>nav<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItem</span></span> <span class="token attr-name">button</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItemText</span></span> <span class="token attr-name">primary</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Trash<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">ListItem</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItem</span></span> <span class="token attr-name">button</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItemText</span></span> <span class="token attr-name">primary</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Spam<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">ListItem</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">List</span></span><span class="token punctuation">></span></span>

This pattern is very powerful and allows for great flexibility, as well as a way to interoperate with other libraries, such as your favorite routing or forms library. But it also comes with a small caveat!

Inlining & caveat

Using an inline function as an argument for the component prop may result in unexpected unmounting, since a new component is passed every time React renders. For instance, if you want to create a custom ListItem that acts as a link, you could do the following:

<span class="token keyword">import</span> <span class="token punctuation">{</span> Link <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-router-dom'</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">ListItemLink</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> icon<span class="token punctuation">,</span> primary<span class="token punctuation">,</span> to <span class="token punctuation">}</span> <span class="token operator">=</span> props<span class="token punctuation">;</span>

  <span class="token keyword">const</span> <span class="token function-variable function">CustomLink</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Link</span></span> <span class="token attr-name">to</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>to<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItem</span></span> <span class="token attr-name">button</span> <span class="token attr-name">component</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>CustomLink<span class="token punctuation">}</span></span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItemIcon</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>icon<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">ListItemIcon</span></span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItemText</span></span> <span class="token attr-name">primary</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>primary<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">ListItem</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

The solution is simple: avoid inline functions and pass a static component to the component prop instead. Let's change the ListItemLink component so CustomLink always reference the same component:

<span class="token keyword">import</span> <span class="token punctuation">{</span> Link<span class="token punctuation">,</span> LinkProps <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-router-dom'</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">ListItemLink</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> icon<span class="token punctuation">,</span> primary<span class="token punctuation">,</span> to <span class="token punctuation">}</span> <span class="token operator">=</span> props<span class="token punctuation">;</span>

  <span class="token keyword">const</span> CustomLink <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useMemo</span><span class="token punctuation">(</span>
    <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span>
      React<span class="token punctuation">.</span>forwardRef<span class="token operator">&lt;</span>HTMLAnchorElement<span class="token punctuation">,</span> Omit<span class="token operator">&lt;</span>RouterLinkProps<span class="token punctuation">,</span> <span class="token string">'to'</span><span class="token operator">>></span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token function">Link</span><span class="token punctuation">(</span>
        linkProps<span class="token punctuation">,</span>
        ref<span class="token punctuation">,</span>
      <span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Link</span></span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>ref<span class="token punctuation">}</span></span> <span class="token attr-name">to</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>to<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>linkProps<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">[</span>to<span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItem</span></span> <span class="token attr-name">button</span> <span class="token attr-name">component</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>CustomLink<span class="token punctuation">}</span></span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItemIcon</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>icon<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">ListItemIcon</span></span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItemText</span></span> <span class="token attr-name">primary</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>primary<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">ListItem</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Prop forwarding & caveat

You can take advantage of the prop forwarding to simplify the code. In this example, we don't create any intermediary component:

<span class="token keyword">import</span> <span class="token punctuation">{</span> Link <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-router-dom'</span><span class="token punctuation">;</span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ListItem</span></span> <span class="token attr-name">button</span> <span class="token attr-name">component</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>Link<span class="token punctuation">}</span></span> <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>

With TypeScript

To be able to use the component prop, the type of the props should be used with type arguments. Otherwise, the component prop will not be present.

The examples below use TypographyProps but the same will work for any component which has props defined with OverrideProps.

<span class="token keyword">import</span> <span class="token punctuation">{</span> TypographyProps <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@mui/material/Typography'</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">CustomComponent</span><span class="token punctuation">(</span>props<span class="token operator">:</span> TypographyProps<span class="token operator">&lt;</span><span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> component<span class="token operator">:</span> <span class="token string">'a'</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span>
<span class="token comment">// ...</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">CustomComponent</span></span> <span class="token attr-name">component</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>a<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>

Now the CustomComponent can be used with a component prop which should be set to 'a'. In addition, the CustomComponent will have all props of a <a> HTML element. The other props of the Typography component will also be present in props of the CustomComponent.

You can find a code example with the Button and react-router-dom in these demos.

Generic

It's also possible to have a generic CustomComponent which will accept any React component, and HTML elements.

<span class="token keyword">function</span> GenericCustomComponent<span class="token operator">&lt;</span><span class="token constant">C</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>ElementType</span><span class="token operator">></span><span class="token punctuation">(</span>
  props<span class="token operator">:</span> TypographyProps<span class="token operator">&lt;</span><span class="token constant">C</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> component<span class="token operator">?</span><span class="token operator">:</span> <span class="token constant">C</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">,</span>
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span>

If the GenericCustomComponent is used with a component prop provided, it should also have all props required by the provided component.

<span class="token keyword">function</span> <span class="token function">ThirdPartyComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span> prop1 <span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span> prop1<span class="token operator">:</span> string <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span>
<span class="token comment">// ...</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">GenericCustomComponent</span></span> <span class="token attr-name">component</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>ThirdPartyComponent<span class="token punctuation">}</span></span> <span class="token attr-name">prop1</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>some value<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>

The prop1 became required for the GenericCustomComponent as the ThirdPartyComponent has it as a requirement.

Not every component fully supports any component type you pass in. If you encounter a component that rejects its component props in TypeScript, please open an issue. There is an ongoing effort to fix this by making component props generic.

Caveat with refs

This section covers caveats when using a custom component as children or for the component prop.

Some of the components need access to the DOM node. This was previously possible by using ReactDOM.findDOMNode. This function is deprecated in favor of ref and ref forwarding. However, only the following component types can be given a ref:

If you don't use one of the above types when using your components in conjunction with Material UI, you might see a warning from React in your console similar to:

Note that you will still get this warning for lazy and memo components if their wrapped component can't hold a ref. In some instances, an additional warning is issued to help with debugging, similar to:

Only the two most common use cases are covered. For more information see this section in the official React docs.

<span class="token deleted-sign deleted"><span class="token prefix deleted">-</span><span class="token line">const MyButton = () => &lt;div role="button" />;
</span></span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span><span class="token line">const MyButton = React.forwardRef((props, ref) =>
</span><span class="token prefix inserted">+</span><span class="token line">  &lt;div role="button" {...props} ref={ref} />);
</span></span>
<span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line">&lt;Button component={MyButton} />;</span></span>
<span class="token deleted-sign deleted"><span class="token prefix deleted">-</span><span class="token line">const SomeContent = props => &lt;div {...props}>Hello, World!&lt;/div>;
</span></span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span><span class="token line">const SomeContent = React.forwardRef((props, ref) =>
</span><span class="token prefix inserted">+</span><span class="token line">  &lt;div {...props} ref={ref}>Hello, World!&lt;/div>);
</span></span>
<span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line">&lt;Tooltip title="Hello again.">&lt;SomeContent />&lt;/Tooltip>;</span></span>

To find out if the Material UI component you're using has this requirement, check out the props API documentation for that component. If you need to forward refs the description will link to this section.

Caveat with StrictMode

If you use class components for the cases described above you will still see warnings in React.StrictMode. ReactDOM.findDOMNode is used internally for backwards compatibility. You can use React.forwardRef and a designated prop in your class component to forward the ref to a DOM component. Doing so should not trigger any more warnings related to the deprecation of ReactDOM.findDOMNode.

<span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line">class Component extends React.Component {
</span><span class="token prefix unchanged"> </span><span class="token line">  render() {
</span></span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span><span class="token line">    const { props } = this;
</span></span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span><span class="token line">    const { forwardedRef, ...props } = this.props;
</span></span><span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line">    return &lt;div {...props} ref={forwardedRef} />;
</span><span class="token prefix unchanged"> </span><span class="token line">  }
</span><span class="token prefix unchanged"> </span><span class="token line">}
</span></span>
<span class="token deleted-sign deleted"><span class="token prefix deleted">-</span><span class="token line">export default Component;
</span></span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span><span class="token line">export default React.forwardRef((props, ref) => &lt;Component {...props} forwardedRef={ref} />);</span></span>