React component with traits

React component with traits
component traits on example of a duck - swim, walk and fly

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.

pigeon with a helicopter-flight trait

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:

export const MyComponent = withQA()
  .withDefinedUser()
  .withErrorBoundary({ name: 'CustomNameComponent' })
  .withAuthorizationRequired() // show a sign in button for unathorized users instead of content below
  .render(({ currentUser }) => {
    return <div>Hello {currentUser.nickname}</div>;
  });
builder pattern to specify functional component traits

Here is MyComponent is a simple React component that renders authorized user's nickname, but extended with few traits:

  1. withDefinedUser - makes sure currentUser prop provided to component, so we don't need to use "useCurrentUser" hook inside.
  2. withQA - extends component with convenient debug tools in runtime.
  3. 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.

<WithConfirmationDialog
  trigger={<StyledButton>logout</StyledButton>}
  onConfirm={logout}
/>
<WithQA>
  <WithDefinedUser
    render={({ currentUser }) => (
      <Hello greeting="Hello" currentUser={currentUser} />
    )}
  />
</WithQA>
<Hello greeting="Hola" />
HOC examples in React

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).