useReport Hook and some thoughts on good API design for libraries
This post covers how to embed your Microsoft PowerBi reports into your React application using useReport hook from powerbi-report-component. I'll go into some depth of inner working of the package and rational behind useReport
, and some thoughts on good API design libraries.
TLDR; if you just want to use useReport
, here's how it works.
Now, for those interested in why useReport
hook and how it works.
So, what do I mean by good API design?
Let's first see what an API is. API is an acronym for Application Programming Interface. It is a software delegate that allows two applications to talk to each other. When we talk about APIs we are usually referring to REST API or HTTP endpoints.
How does it apply to libraries?
Functions or components exposed by your library are also APIs as, it allows the developer to communicate between their application code and your code that exists in the library.
When I first created the component it allowed you to embed a report in your React application. So it exported React component as a default called Report
.
The library user would've imported it and used it like:
(Note: Microsoft PowerBi actually supports multiple types of embeds: Report, Dashboard, Tile)
This made sense at that time. As it would be used to embed only an Microsoft PowerBi Report
not Dashboard
or Tile
. Soon I added embedType
as a prop to support embedding Dashboard
.
This felt a little odd when you're actually embedding a dashboard
and you still had to use a <Report />
and pass dashboard
to embedType
prop inside your code.
Also, using embedType
to determine what kind of props can be passed to the component made the library code bloated and less clean with the use of switch is multiple places.
Soon I started getting feature requests to support Tile and for Create Report feature.
Each embed type had to support a lot more handlers and other properties.
For example, You can have handler onPageChange
handler on embedType = report
but not in embedType = dashboard
or embedType = tile
or, You can have a pageView
prop for embedType = dashboard
but not for embedType = report
orembedType = tile
.
But, the library just exported a single component (Report
) and propTypes
of the component looked like this.
React component is nothing but a function
and props are nothing but parameters
that you pass in to the function. This means that Report
is one function that you can pass 16 parameters into. Library user can never actually know which parameter to pass in unless they go and see the documentation every time they had to make a change.
User's IDE intellisense also becomes useless as suggestions will have 16 props instead of only right props for their use case.
This parameter list was only going to increase in number as I keep adding new handlers or properties based on feature requests. Exporting a single default export
of a Report
also was not maintainable.
This is an example of a bad API design. As it doesn't compliment the users development environment like the IDE or doesn't help speed the user's workflow.
Having the embedType
as a prop creates confusion here as the user might be using library to embed a Tile
but would still be using it as Report
in their code.
With this in mind. Maintainers and I went ahead refactored the code to export multiple components as named exports. i.e, Report
, Dashboard
and Tile
with their respective props. Now we have multiple named exports based on embedType
which can be imported like. This also made the library code more maintainable.
Now the library can be used like.
And the propTypes
of respective exports look like:
This enables the user to use the library without having to go back and see the docs every time and IDE intellisense will suggest the right props which speeds up the developer's workflow and the user doesn't have to go to see the docs every time they had to make a change.
Since, We made the exports from the library more explicit. User will be able use Tile
in the place where they are embedding Tile
or Dashboard
when they want to embed a Dashboard
. This solves a lot of confusion and is explicit enough to make sense where it's used in your codebase.
A good API design for libraries has to get these right:
- Complement the tools available in the end user's development environment.
- Explicit enough to make sense where it's used.
One way to compliment the user's development environment is enabling IDE intellisense to speed up user's workflow. i.e, by making exports explicit or using right propTypes
like what you just saw.
Another way would be to support multiple ways to use your library based on user's development environment.
The library supported embedding your reports using components by exposing React components. This is one way of sharing code or using the API.
Now, React has hooks to share code. (Read more about it here)
I refactored the library to use the React hooks and it internally looked like this:
Since, React now enables sharing of code as a hook. I can remove a lot of library specific code and expose this hook for the end user.
See the full code here
By exporting useReport
from the library the user has more than one way to use the library. This adds to the value of the library as it complements the users development environment by the use of React hooks.
The hook exposes an internal function as an API for end user to get the most out of the library and by using the hook, the user can choose to reduce the extra abstraction and logic that comes along with using the API with an component.
This was the rationale behind the useReport
hook and my thoughts on good API design for libraries.
Next step in the quest to improve the API design for the library would be add typings and support types. i.e, to add support for Typescript (In progress). In the next post I'll cover some basics of Typescript and more on migrating the library to Typescript.
Meanwhile, If you're interested seeing more of the code or contributing you can head to https://github.com/akshay5995/powerbi-report-component/
PRs or issues welcome for the library.
Thanks for your time!