Component API Flexibility in React
Pattern libraries, component-based design systems — whatever you want to call them — it’s no secret that they improve user experience by increasing visual and functional consistency. Most designers will tell you that consistency is a key design principle, so how much flexibility, if any, should be afforded to users who want to customize the components in your design system?
In this post I will discuss an area of component API design where the balance between consistency and flexibility comes into play. The approach you take will likely depend on the level of flexibility you intend to provide.
In React, there are several ways to compose child elements. Some solutions provide the consumer with more flexibility than others. Let’s look at a few options.
Array or object prop
object prop to compose child elements is the least flexible option because the component controls the type of children that are rendered and their props. This approach is suitable for component libraries that prioritize consistency over flexibility because the component controls all aspects of rendering.
Here the component is saying: Just give me the data — I’ll take care of rendering the children.
In this example, the consumer provides an array of items and the
Tabs component decides what to render. This makes it difficult for the consumer to override the appearance and behavior of the tabs.
Flexibility: 🧘♂️ 🧘♂️ 🧘♂️
children prop to compose child elements is idiomatic in React. This approach is a bit more flexible because it lets the consumer render the children. However, the result is a tight coupling between the component and its children. For the component to control its children, they must expose the necessary props.
Here the component is saying: Give me the children and I’ll slot them in somewhere. Just make sure they expose the required props so I can set them.
In this example, the consumer wants to use
FancyTab components instead of
Tab components. The only reason this works is because
FancyTab exposes the required props
Flexibility: 🧘♂️ 🧘♂️ 🧘♂️ 🧘♂️ 🧘♂️
Using a render prop to compose child elements is an advanced pattern that delegates all aspects of rendering to the consumer. Furthermore, it results in a loose coupling between the component and its children, allowing the consumer to render children with an otherwise incompatible API.
Here the component is saying: Render the children when I call this function. I’ll expose an API to help you integrate your super-custom child components.
In this example, the consumer wants to use custom
HoverTab components, but there’s one problem: The
HoverTab component exposes an
onMouseEnter prop instead of
onClick and an
active prop instead of
selected. The consumer needs a way to adapt one API to another. This is where render props come in handy.
Tabs component is rendering, it invokes the
children prop (now a function) and passes it
setSelectedIndex. This allows the consumer to map the
HoverTab props to the API provided by the
See also: Inversion of Control
Which approach is best?
It depends whether your component library prioritizes consistency or flexibility. If you want to lock-down the design and behaviour of your components then an
array prop might be best. If you want to provide your users with ultimate flexibility, then go for a render prop.