React component with traits
Trait means behaviour or some way of appearance if we talk about UI. For example a duck has traits to walk, swim, fly and others. Similarly, components in our programs have their own behaviour. Imagine component button withConfirmationDialog
behaviour (trait) or a menu tab withBadgeCounter
appearance (trait).
Composition over inheritance
To understand traits more, let's first remember classic simple idea of inheritance from OOP world - behaviour and properties of an object are inherited from its parent. That is why a puppy can walk and bark similarly to its parent dog.
In programming world, things are more agile and we don't need to inherit objects to reuse a specific behaviour. This better way of reusing behaviour is achieved through a composition of traits.
Idea of trait is to extract behaviour and properties from component and reuse where needed easily. So, we can take a fish and extend it with walk
trait - and it will walk! In virtual world we don't need a parent to inherit from, just get a behaviour and apply to any needed component of a program.
Adding new methods to objects is easy for JavaScript - just assign needed method to instance and voila. Some languages like Scala and PHP even made traits as part of language syntax.
But how to apply traits to functional component of React? First approach
In functional programming a trait can be expressed as a function wrapper.
Let's consider next example using builder pattern:
Here is MyComponent
is a simple React component that renders authorized user's nickname, but extended with few traits:
withDefinedUser
- makes surecurrentUser
prop provided to component, so we don't need to use "useCurrentUser" hook inside.withQA
- extends component with convenient debug tools in runtime.- and others
Remember those old days, when react-router
suggested to use withRouter
? That's another practical example of a component trait! And withRouter
is called here a "high order function" as it takes other function as argument and returns another function.
While this approach may look concise it is actually a tricky and cumbersome to implement and sometimes hard to debug rendering properly when multiple layers of traits composed to a component. Especially heavy part is for TypeScript apps where we need to forward and extend lots of generics till the needed component's rendering content - that's why I let you figure out yourself how can this builder pattern can be coded in reality.
HOC is the way to go. The winner
High order components in React works great to express traits composition. HOC is also a function wrapper actually but it leverages all the beauty of JSX syntax.
Reasons to use HOC instead of builder pattern or other high order functions:
- Better readability due to JSX (especially when lots of arguments configure HOC behaviour)
- Semantically proper tree in virtual DOM due to component names without hidden layers of intermediate components created by high order functions from first example
- Idiomatic way for React to compose functionality of components
- Easy to implement - just regular components with children
Because of that, I mostly use HOC to compose traits on top of my base components while builder pattern is rarely used and just for simple extensions (usually because of existing code style for a particular component).