Optimize APIs for change

Published
2023-10-10
Tags
ReactDesign Systemsarchitecture

Good design systems and component libraries are often being optimized for the initial UI creation: it should take a minimum amount of time to introduce a new feature. I think that it’s an important metric but even more important is to anticipate the future changes of the requirements and provide APIs that do not require large refactorings.

Recently we had a discussion about a new Popover APIs and we wanted to make the API as simple as possible. We ended up with something like this:

<Popover
  triggerEl={<Button>Toggle popover</Button>}
>
  Popover content
</Popover>
 

It’s very easy to write it and does work as expected out of the box. But what happens if we change the requirements slightly by saying “when the popover is open, we want the button’s label to change appropriately”. Since the component’s state is encapsulated in the Popover component, we can’t really implement this easily.

Our Popover component can be used as a controlled component, so you might end up with this code:

const [open, setOpen] = useState(false)

<Popover
  triggerEl={<Button>{open ? "Close" : "Open"} popover</Button>}
  open={open}
  onOpenChange={setOpen}
>
  Popover content
</Popover>

That might not seem like much, but imagine you need to introduce the state and do this refactoring for hundreds of different popover instances in the app! That’s not something you can do easily.

Although the initial effort to adopt the Popover was small, the effort needed to change the UI ever so slightly is much larger.

What if we could write something like this instead?

<Popover
  triggerEl={(state) => <Button>{state.open ? "Close" : "Open"} popover</Button>}
>
  Popover content
</Popover>

This is a much smaller change needed to go from initial implementation to the enhanced one!

Conclusion

As library authors, it is crucial for us to carefully consider and anticipate any potential requirement changes that may arise when designing public APIs. This is because the success and usability of our libraries heavily rely on how well they can adapt and meet the evolving needs of developers and users alike.

By proactively addressing these potential changes, we can ensure that our APIs remain relevant, flexible, and capable of supporting a wide range of use cases and scenarios.