pinia-fetch-store: a tiny library for request state management with Pinia

Lumbardh Agaj
JavaScript in Plain English
7 min readMay 10, 2024

--

Eliminate boilerplate code when fetching data with Pinia library

Image Source: https://unsplash.com/photos/black-flat-screen-computer-monitor-8qEB0fTe9Vw

The main motivation behind writing this article was the fact that I was not able to find a library that would help me with request state management and that worked well back when I was working with Vuex modules (this was a few years ago). During that time, I started implementing a library that utilized reusing Vuex modules only for Pinia to become the default state management in the Vue ecosystem. Later, I ran into Redux Toolkit Query (RTK) and decided to re-write the initial library which I had written for Vuex so that I could inherit an API similar to RTK. Then, I followed up and wrote pinia-fetch-store library which I will be discussing in this article.

A quick introduction to Pinia

Pinia is a state management library in Vue that saves the application state using stores. Each store can be thought of as a slice of application state. The store is composed of state, getters and actions. Here’s a basic implementation of a store that manages the state of a counter.

You can start consuming the store in your UI components such as dispatching the increment() action when user selects increment button. To read the count value you can directly use the store state. And, in case we want to know what the next count value will be, we can use the nextCount getter method. Please find below the store consumption using a trivial Counter component written in Vue.

A verbose approach

Typically, a web application is composed of many components, where each component most likely will be interested to read some information from the server. Then, most of the times we will be interested to track the state of each request, such as whether or not the request is loading, has failed or has succeeded. The primary motivation behind going at such length is to deliver a better user experience. Here’s how a todo pinia store could look like when we start tracking the state of each request from scratch. In this case we want to track the state of the following requests: create, list, update, and delete todo items.

Then, our main UI component which lists todo items in a table with toggle and delete button columns will be interested about the state of the requests which it will host so that it can react appropriately. For instance, while the list of todo items is being fetched from the server we want to display a loading spinner in the table.

There are quite a few problems with this approach and I don’t think it’s difficult for one to tell. To begin with the code boilerplate is overwhelming, especially if the number of stores that are interested to request information from the server starts piling up. Moreover, maintenance wise it will be painful to make a change that spans across the stores due to the sheer amount of WET code that the approach introduces. Last and most importantly we want to separate the concerns for tracking the state of a request from the implementation of the task at hand because they add unnecessary noise.

Introducing pinia-fetch-store library

pinia-fetch-store library introduces an abstraction layer on top of the defineStore() method found in Pinia library. The source of inspiration for the design of the library has been the Redux Toolkit Query (RTIK) library API.

pinia-fetch-store library is small, easy to use (it has a single export, namely createFetchStore() method), neverthless it can be very handy (I know I would have loved to have a similar library when working with Vue and it goes without saying that this is a completely biased opinion).

Installation

You can use any of your preferred package managers to install the library.

  npx install pinia-fetch-store

yarn add pinia-fetch-store

Getting started

The library’s sole export createFetchStore() method accepts an object parameter with ‘endpoints’. ‘endpoints’ is a function callback which must return an object literal with your defined API endpoints. Each property in endpoints corresponds to a server request which later can be dispatched as action from a UI component or from the stores itself.

In order to implement an endpoint, one has to use the EndpointBuilder object which is passed down from the endpoints callback function. In return, you use the create() method of EndpointBuilder to write the logic of retrieving data from the server. EndpointBuilder’s create() method is generic and it expects the user to declare the return type of the endpoint as well as the type of the parameter (if any). Then, as a parameter it expects an object that has requestFn() method implemented. requestFn() does what it actually describes, communicates with the server to fetch information that it needs. In other words, it is the action which we will dispatch primarily from UI components. Please find below the equivalent todos store when using the pinia-fetch-store library.

Dispatching actions is similar to what we have seen before, e.g. to invoke the action that lists todo items we can use store.listAction() method. Please notice the postfix for all pinia fetch store actions is set to ‘Action’. If the action has a payload then it will be passed down to requestFn() method as parameter e.g. store.deleteAction(‘123’) will actually invoke the ‘delete’ endpoint found in todos store and pass ‘123’ as id parameter.

To retrieve the information for each request you can do that either directly from the state of the store or using the getter methods, e.g to get response data of the list endpoint we can use store.listComputed.data. Again, it’s important to notice the postifx for all pinia fetch store getters is set to ‘Computed’.

There are no required changes on TodoList Vue component, which we saw earlier after we start consuming the todo store created with pini-fetch-store library and most importantly, there are no more request state management leaking concerns when implementing our data communication layer.

Conclusion

Pinia does an excellent job when it comes to managing the application state by making use of stores. Its simple programming interface makes it easy to write libraries similar to pinia-fetch-store.

pinia-fetch-store should be able to suite your needs for any small-to-medium size application. You can find more information about the library using the link below.

https://github.com/AgajLumbardh/pinia-fetch-store

Appendix A: Caching

Ideally we would want to re-use the data in the store as much as we can for as long as it’s valid. As with any cache the problem is data invalidation. Building on the library API which we have currently designed we can extend its functionality to implement a basic cache solution. The caching mechanism will try to accomplish two things:

  1. Anytime the data is found in the store re-use it (do not fetch it).
  2. Retrieve the latest data in the store only when it becomes invalid.

The first feature will not require library API changes. Therefore, the focus will be on the mechanism which the user can use to flag data staleness.

Drawing from RTK as a source of inspiration we can tweak our EndpointBuilder class so that instead of using create() method we can seprate it into query() and mutation() methods to tell the type of endpoint request. query() method will indicate that the request will only read information from the server. In contrast, mutation() method will indicate that the request will try to change data in the server. Now that we can tell which endpoints can invalidate the cache, we will embolish query endpoint methods with ‘providesTag’ property, which is a unique string or token to identify a query endpoint. Similarly, mutation endpoints will have ‘invalidateTags’ property (an array of strings) that will indicate which provided tags become invalid. Please find below a concept of the caching mechanism in pinia-fetch-store.

It’s important to notice that with the cache mechanism in place any mutation endpoints such as “create” does not have to invoke the “list” endpoint after its successful completion. In this case, the library would be able to tell that the ‘todos’ tag has become invalid and that it should automatically fetch the latest data from the server.

This cashing mechanism is well versed for stores when used independently (not used in combination).

Appendix B: vuex-fetch-module library

For those who are still using Vuex and would like to introduce a similar library such as pinia-fetch-store in their projects, I have written vuex-fetch-module library. It’s the original library which I wrote and which actually started the ball rolling for me. You can find more information about the library using the link below.

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

--

--