Is Prop Spreading in React Really an Anti-Pattern?

There's a lot of discussion online about whether or not prop spreading in React is an anti-pattern. Prop spreading is the use of the JavaScript spread operator ... with React props.

function MyComponent() {
  const props = {
    count: 5,
    label: 'Count',
  }

  return (
    <ChildComponent {...props} />
  )
}

The obvious pros of this pattern are that it can massively reduce the amount of code needed to pass the props object into the child component. The downsides can be a little less obvious and less clear. In fact, it's quite common to completely disallow prop spreading with an ESLint rule. I think this is a bit overkill, but I can see why some choose this option.

First of all, some of the problems with prop spreading are non-existent if you use TypeScript. Without TypeScript, it's difficult to see at a glance what is getting passed into a component with prop spreading. With TypeScript it's as easy as hovering over {...props}.

Another criticism of prop spreading is it can cause unnecessary re-renders. If you pass in unnecessary stateful values your component will re-render whenever they change, even if the component doesn't use those variables. In my opinion, this issue is easy to avoid unless you're being careless, and with TypeScript this issue is even less likely to happen.

There are also times when prop spreading is the only good option, which is another reason why I disagree with the eslint rule. For example, when building a design system in React, if you care about web semantics and accessibility, most of your components should be built around standard HTML elements. HTML elements can have an enormous number of attributes, such as ARIA attributes, and writing semantic, accessible code requires using them. Let's say you want to have a Button component, built around the HTML button element. Generally, you should strive to have your custom component's API have feature parity with the built-in component at the very least. In React, your options are limited to passing dozens of props manually, making your code less readable, more rigid and generally harder to maintain or prop spreading.

If you've ever taken this approach when building a React design system, you'll find that it's extremely useful, but has some limits. Let's go back to our button example. Our Button component right now is essentially a thin wrapper around the HTML button element. You can pass in all the same attributes as the HTML button element, but our component has some styles applied to it. Here is the code:

type Props = JSX.IntrinsicElements['button']


function Button(props: Props) {
  return <button {...props} className="btn" />
}

You might be wondering, what's the issue with this? Well, any classes that we pass into Button will get overridden by "btn". A quick and dirty solution to this is as follows:

type Props = JSX.IntrinsicElements['button']


function Button({ className, ...props }: Props) {
  return <button {...props} className={`btn ${className}`} />
}

Here we are now extracting className from our props and combining it with our own "btn" class before passing it to button. This will work, but it isn't the best solution. For one, when className is undefined, it will include "undefined" as a class. But more importantly than that, what happens when I want button to have other default props such as inline styles, refs, event handlers, etc. Should we handle these "merges" manually each time, or could there be a better solution?

I'm not the first to build a library for merging react props. There are several on npmjs.com, and there's even one in the popular react-aria library. My library, prpx, is designed to tackle this problem by intelligently merging Tailwind class names, removing duplicate class names, merging styles, combining refs, and chaining event handlers. I plan to continue updating this library as I find more edge cases, and as I hear feedback from other developers.

Is prop spreading really an anti-pattern? My answer is no. Yes, you can make big mistakes with it, but if you use TypeScript and a prop merging library it can be a really powerful and beneficial pattern, especially for design system code. That said, I think you should always consider if prop spreading is necessary before using it, and should generally avoid it for app-level code.