Composable patterns in react #take-2
A functional approach to building react applications. How to make API calls better ? Here’s #take-1 if you have missed.
The Unix philosophy, originated by Ken Thompson emphasizes building simple, short, clear, modular, and extensible code that can be easily maintained and repurposed by developers other than its creators. The Unix philosophy favors composability as opposed to monolithic design
So its not surprising that Douglas Mcllroy came up with the proposal of Unix pipelines. He thought like a mathematician. I believe this was the birth of functional programming (don’t quote me on that). The core building block of FP has always been composition. Now grep, awk and ps
are all small programs which just do 1 thing. However, because of the way every program is designed in Unix, they can be composed to create much more complex programs. Look at the following snippet.
List the process IDs (PIDs) for all systemd-related processesps -ef | grep systemd | awk '{ print $2 }'
It creates a pipeline starting from ps -ef
passes the output to grep systemd
to find all systemd processes and finally awk '{ print $2 }'
that prints the second column of the resulting table.
In #take-1 we explored different flavours of json-api-client
. Now lets take it up a notch
User Story: Given i am on home page, when the page loads, users from an api should be shown in a table below
Now we already have a json-api-client
. We also have a useJsonApi
hook that can load data in state causing the page to re-render. To summarise what we had.
jsonApiClient
useJsonApi
Can we build upon these 2 blocks. Yes we can :) Without further ado
useJsonApiOnLoad
If you are familiar with react, all we need to do for doing things on load is to wrap things inside useEfffect
. And that's exactly what we did. The useJsonApiOnLoad
hook takes in any hook that adheres to UseJsonApi
contract. Under the hood, we need to memoize the api. This is useful for child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate). And once we have a memoized api, we wrap it inside useEffect
. Easy peasy...
Note: Don’t worry too much about ApiResponse & Omit<T, keyof UseJsonApi>
. We will get to that later.
The App
And there’s more
Assumption: User is created asynchronously by some other process. So its possible the api would return a
404
while that operation is still in progress.
User Story: Given i am on home page, when the page loads, users from an api should be shown in a table below.
We can reuse all the flavours of json-api-client
that we saw earlier.
Assumption: We have some mechanism to get the access token for making api calls.
User Story: Given i am an authorised user on home page, when the page loads, users from an api should be shown in a table below.
We already have useJsonApi
hook that deals with storing the api response in state. We also have useJsonApiOnLoad
to make an api call on page load. All we need is a missing piece to add authorisation header when that api call is made. Lets create that missing piece
useAuthorisedJsonApi
All we are doing inside the hook is using the accepting UseJsonApi
payload and overriding the callApi
implementation to add authorisation headers. These auth headers could be fetched using any mechanism
Now it’s getting interesting. Lets take a look at how we wire things up
The App
If we wanted to make an authorised api call on a button click instead of page load, all we have to do is compose things we want. Just that the headers will be passed when callApi
is invoked on button click.
The App
And again we can use any flavour of jsonApiClient
under the hood
But how was all of this possible. Remember this ?
Note: Don’t worry too much about
ApiResponse & Omit<T, keyof UseJsonApi>
. We will get to that later.
Now look at the code for useJsonApiOnLoad
again
For our contract, instead of relying solely on UseJsonApi
contract, we relied on any specification that extends UseJsonApi
. What this allowed us to do was to accept any other data that was passed in, and pass it back to the caller in the response. And we also made it type safe by saying that the response will contain ApiResponse
and anything that was extra on UseJsonApi
apart from the core contract :)
We are not stopping just here. Lets spice things up even more.
User Story: Given i am on home page, when the page loads, i want to show a loading indicator till the time data is fetched from the api.
We already have the core building blocks with us.. viz… jsonApiClient
, useJsonApi
and useJsonApiOnLoad
. All we need is for someone to maintain a loading
state. And then find a way to stitch it all together. Without further ado...
useJsonApiStates
Again the code is extremely straight forward. We still rely on the core UseJsonApi
contract. All we do is maintain a loading
state locally and turn it on and off before and after the api call is made. That's it.
Note: This can be easily extended to error states as well. I will leave that to your imagination.
So far we haven’t created any abstractions for react components. Lets use this opportunity to introduce one
SuspenseOnLoad
Once we have stitched the required hooks, all we do is subscribe to the loading
state. If its loading, show the loading component. And when the data is present, show the data :D. I have purposefully kept the client as an input param. This allows us to pass any flavour of the client inside. Also there is absolutely nothing stopping us from adding useAuthorisedJsonApi
to the mix.
The App
And just to get closure.
User Story: Given i have a button
Fetch
, on the page, when i click onFetch
, i should see a loading spinner before the data is fetched from the api.
We can take all the inspiration we need from SuspenseOnLoad
SuspenseOnTrigger
As we are not relying on the page load event, we need a trigger component to start things for us. Apart from that the code should be self explanatory.
The App
And that is a wrap. Its essentially the power of composition. It has enabled us to create new abstractions on the fly by composing existing one’s. The concept of composition has always been dear to my heart. Hope you folks find it useful. Cheers.