Writing a Custom React Hook for Spotify's Web API (Implicit Grant Flow)

As one of the most popular music streaming services, Spotify encourages developers to craft new and engaging experiences for music lovers around the world. With over seventy endpoints, Spotify's Web API allows external applications to access Spotify's vast catalog of albums, artists, playlists, tracks and podcasts and detailed profile information about the current authorized user. All of this data opens the door to building applications that explore and analyze the data, generate new insights from the data or present the data in exciting new ways.

Want to discover new artists based on the artists of the tracks in your playlists? Use the GET https://api.spotify.com/v1/artists/{artist_id}/related-artists endpoint to fetch artists related to an artist based on the analysis of other users' listening histories. Want to re-imagine the user interface for searching and displaying tracks that match a query? Use the GET https://api.spotify.com/v1/search endpoint to query and retrieve results from Spotify's tracks catalog.

If you decide to create a Spotify-connected application with React, then centralizing all Spotify-related logic inside of a single, reusable custom hook, named useSpotify, eases the integration of Spotify into the application. This organizes everything Spotify-related in one place. For a component to communicate with Spotify's Web API, just import the useSpotify hook and call the hook within the component. Whenever Spotify updates its API with new endpoints or modified response objects, you edit one single file in the codebase: useSpotify.js. The hook enables all components to get the authorization status of the user and interact with Spotify's Web API.

Below, I'm going to show you...

  • How to write a custom React hook for Spotify's Web API.

  • How to use the hook via a create-react-app demo application.

Overview of the Custom Hook#

Imagine building a dashboard that features a view for searching tracks from Spotify's catalog, a view for managing the current authorized user's saved albums and tracks, etc.

For the custom hook useSpotify to support this type of application, it must handle:

  • Authorization (obtained from the Implicit Grant Flow).

  • Making the current authorized user's information available to all components within the application.

  • Sending requests to Spotify's Web API.

  • Processing data from responses sent back by Spotify's Web API.

To begin, let's briefly recap React hooks and how they work.

Hooks provide us a way to reuse stateful logic. When multiple components use the same hook, the state within the hook is not shared by those components. Therefore, we will need a provider component to wrap the application and make the hook's object available to any child component that calls the hook with context.

Anytime the user decides to "log in" or "log out" (represented as having or not having a valid access token respectively), the changes to the hook's state will cause those components to re-render.

Here's the base template of the hook, which exports the provider component <SpotifyComponent /> and a hook that returns the current context value, which comes from the object passed to the value prop of the provider component.

(useSpotify.js)

All of the Spotify-related logic will reside within the useProvideSpotify provider hook, which creates the spotify object that's fed to the provider component, exposes methods for working with Spotify's Web API and manages state variables like token and user.

Authorization with the Implicit Grant Flow#

To obtain authorization, the hook will implement methods for facilitating the Implicit Grant Flow. Although the Implicit Grant Flow does not provide a refresh token, it requires only the client-side application to carry out the entire authorization flow. No additional backend code needed!

Upon being granted authorization, Spotify issues a short-lived access token that expire within 3600 seconds (1 hour). Once the token expires and is no longer valid, you must complete the entire flow again to obtain a new access token.

To start, let's write a login method that opens a popup that prompts the user to...

  1. Log into their Spotify account.

  2. Review the scopes (e.g. user-read-private and user-read-email) that determine the amount of access the application will have to their account's data.

  3. Agree to authorize the application.

(useSpotify.js)

Upon authorization, the user is redirected to the redirect URI (e.g. http://localhost:3000/callback) within the popup. The redirect URI contains a hash fragment with the access token, its type, its expiration time period and state (e.g. http://localhost:3000/callback#access_token=xxxx&expires_in=3600&token_type=Bearer&state=xxxx) encoded as a query string. When this page loads and its component is mounted, extract the access token and its metadata from the query string and store the token and its expiration timestamp in local storage. Then, call the main window's spotifyAuthCallback method, which was registered back in the login method, to close the popup and update the hook's state with these values.

(useSpotify.js)

By storing the token in local storage, we don't need to constantly ask the user to re-authorize themself if they decide to close the browser, reopen it and revisit the application less than an hour later. And, if the user does revisit the application more than an hour later, then knowing the expiration timestamp makes it possible to invalidate the token and ask agains for authorization.

(useSpotify.js)

Note: Any value stored in local storage is stringified and must be converted back to its appropriate typing.

Note: Yes, it's not recommended to store access tokens in local storage due to cross-site scripting (XSS) and possibly being read by malicious JavaScript code. Only do this for demos. For production-grade applications, I recommend going with a different authorization flow (one that involves the server-side and setting the token within an HTTP-only cookie).

Anytime the user decides to "log out," invalidate the token and reload the page so that it automatically redirects them to the homepage.

(useSpotify.js)

To restrict the redirect URL to only be visited within the popup by Spotify, let's write a method to check whether the redirect occurs within a "valid" popup that originated from the application itself.

(useSpotify.js)

The useHistory hook from react-router-dom gives a history instance to verify its length is at least two (one entry for Spotify, the other for the redirect).

Once the token and expiration timestamp state variables are updated, the hook should automatically fetch the current authorized user's information from Spotify and update the state with this information.

(useSpotify.js)

For the user to be considered "logged in," the hook's state must the token and user's information, and the token cannot be expired.

(useSpotify.js)

Interacting with API Endpoints#

To get data from Spotify's Web API, define a method for each endpoint. For this hook, we will only define two methods: one for the GET /me endpoint and another for the GET /search endpoint.

Any request sent to the API must have the access token added to its Authorization header to allow Spotify to identify the user and know that the requesting application has permission from the user to access to their data.

(useSpotify.js)

The callEndpoint method serves as a helper method that keeps the endpoint methods' definitions DRY.

For more endpoints, visit Spotify's Web API reference here.

Final Touches#

When the user performs a full-page refresh of the application, it takes time for the application to fetch the user's information. The hook should provide a flag that notifies components when the application is "loading" so that they can present a loading indicator to the user.

The application is in a loading state when the hook is initialized, and it is finished loading when it has successfully (or unsuccessfully) fetched the user's information from Spotify.

(useSpotify.js)

The hook will expose the following state variables and methods to child components that call it:

(useSpotify.js)

Using getters allows the component to reference the method call like a flag. So instead of calling hasLoggedIn(), just use hasLoggedIn in the component.

Altogether...

(spotify.js)

(.env)

(useSpotify.js)

Want to see how this hook works in an actual application? Clone the demo application here and run it locally.

Next Steps#

Try using this hook for your next Spotify-connected application!

Sources#