One of my favourite uses of hooks is to consume APIs in my applications. It provides much more than an organized place to house API logic — it allows the API logic to dynamically respond to state changes within any view component that it’s dropped into.
Let’s go through one example of moving from an inline API call to a well-crafted hook using a simple weather app that I put together.
Feel free to follow along with the repo.
Step 1 — Fetching API data inline
Let’s start with a less-than-ideal version — having all API logic in the view component itself.
Our API call is triggered from a
useEffect with two dependencies — meaning it will be called in two situations:
- When the coordinates initially arrive from the browser, and
- Any time the units change (i.e. the user swaps from Celsius to Fahrenheit).
This is great! By the end of this article, we’ll make sure to retain this behaviour, but move the logic out into its own weather service.
(Note that it may seem ridiculous to declare fetchWeather only to call it directly afterwards — but this is the only way that React allows async functions to be called within useEffect blocks.)
How can we improve this version?
I’d rather not call an API directly from my component. It’s ugly, and it distracts me from using components as I’d like to — as composable building blocks. I’d much rather have a dedicated Weather Service that is responsible for everything weather-related.
Step 2 — Extracting the logic to a hook
So as a first step, I’m going to move my weather logic out of the view component and just call
getWeather when the coordinates arrive from the browser:
To do this, I’ve created a new
useWeatherService hook that returns two objects — a stateful
weather object (that contains the weather), and a
getWeather function that can be used to trigger a new API call.
But it’s not a very clever hook — our view component is still responsible for triggering API calls. Instead of being a smart API consumer, the hook is merely a house for our weather logic. So while we’re making our application cleaner and more modular (and more testable), we haven’t yet leveraged one of the more powerful features of hooks — the ability to declare
useEffect functions that will run in whatever components the hooks are initialized.
Step 3 — Making Better Use of the Hook
And so as a last step, we’ll make our hook into a genuinely useful and shareable one, and extract the lifecycle logic from the component and into the hook itself.
Instead of the component triggering the API call, the hook will listen for the arrival of coordinates or a change in units, and then trigger the API call itself.
Here is the final hook, in full:
Notice that we’ve retained our initial API triggering logic — when the coordinates or the selected units change, we’ll send off a new API call. But now, the logic is outside of our view component — the weather service is responsible for re-triggering itself, while the view component just passes it what it knows — the coordinates and the selected unit.
To consume the hook, all we need to do is initalize it while supplying it with the current coordinates and the current selected units:
What may seem counter-intuitive here is that although we only call
useWeatherService once with the two arguments, the hook will continue to listen to any changes in the arguments and respond by changing the value of the
weather variable. This is something that confused me at the beginning - but it works.