Skip to content

Tips and tricks: nesting vs. combining via polymorphism

Ernesto García edited this page Sep 7, 2021 · 2 revisions

The design system components have this ability where you can render one component as a certain HTML tag:

<Stack as="ul">
  {tasks.map((task) => (
    <Box as="li" key={task.id}>{task.content}</Box>
  )}
</Stack>

The main purpose is to be able to take advantage of the new components while at the same time not dropping the ball on using semantic HTML.

However, it can also be used to render as another React component:

// Link below can be thought of as the Link component in react-router
<ButtonLink as={Link}>
  Click me
</ButtonLink>

This is useful to be able to get the functionality of one component with the visuals and functionality of another component. In the example above, we render a link that has the visual look and feel of a Button from our design system, but in which the link functionality is provided by react-router, allowing this link to perform SPA-like navigation on the client-side instead of a full server round trip.

Caveats

With great power, comes great responsibility. You should not abuse this feature for markup optimization purposes.

What's markup optimization? Take the following example, for instance:

<Columns space="medium">
  <Column>
    <Stack space="small">
      <One />
      <Two />
    </Stack>
  </Column>
  <Column>
    <Stack space="small">
      <Three />
      <Four />
    </Stack>
  </Column>
</Columns>

We could be tempted to say that each Column could also be the Stack, thus removing one extra container element and simplifying your markup.

<Columns space="medium">
  <Column as={Stack} space="small">
    <One />
    <Two />
  </Column>
  <Column as={Stack} space="small">
    <Three />
    <Four />
  </Column>
</Columns>

This is not guaranteed to work 100% of the time because styles could clash.

In the above example, for instance, the div generated by each Column element will have margin applied to it to achieve the space="medium" effect. And the div generated by each Stack will have margin applied to it to achieve the space="small" effect of the Stack. You'd think is not the same margin in the same direction, because stacks add vertical spacing while columns add horizontal spacing. However, columns add vertical spacing also, for when they show in collapsed form due to the viewport getting narrower (i.e. responsive design).

You may think: what if I have not instructed a particular Columns element to collapse on responsive viewport sizes? The styles are there all the same, just in case. We could think of optimizing this and not applying the styles except to collapsible columns, but the main caveat warned about here remains: in general, combining two React components into one via polymorphism may break things, especially if both components have similar responsibilities regarding style and/or functionality.