I have a client-side-only React app that uses hooks, context, and local storage to implement user logins and session management. It’s a typical setup with users logging in via an OAuth provider, getting an identity token then exchanging it with our servers for access and refresh tokens. The session state and token pair are kept in a top-level context provider and persisted/rehydrated via local storage.
I now need to add an API client that will be used to make RESTful calls to several services. The data from these calls will feed most of the front-end actions. The client will be responsible for automatically refreshing the access token when it expires, redirecting to the /login page if the refresh token expires, and providing standardized error and parameter handling.
There are a few ways I could implement the client, but my initial idea was to create it as a top-level context that sits just below the user session
context. Any components that need to make API calls would do so via a const {getAbc, postXyz} = useContext(ApiContext)
call.
Are there any downsides to this approach? Since the ApiContxt
isn’t really holding any stateful data, its usage shouldn’t cause unnecessary component renders, while still allowing the ApiContext
access to the UserSession
information.
Are there any better alternatives? I thought of using custom hooks, but I want to have all of the endpoints available as named methods with unique parameters that are independent of the actual URLs. It seems like to do this I would need a new hook for each endpoint.
2
Answers
I implemented something quite similar to this on react recently. We went with a custom hook for handling the api calls.
That hook used axios and that allowed us to do error mapping and other things inside the hook before returning a response/error to the component.
I found this approach didn’t cause any unnecessary side effects and easier to maintain.
Your idea of using a top-level context for the API client would work. However since you’re not storing any state, this is not something that I would advice, instead I would use either of the following approaches:
Use custom hooks: create custom hooks for each API call, this allows components to use only the hooks they need and you’re free to customize and scale your custom hooks individually whenever needed.
Use React Query: It comes with hooks for fetching, caching, and synchronizing data from APIs and you can also take advantage of automatic retries. On top of that, it would hugely reduce boilerplate code so you’ll end-up with much tidier code base.