An attempt at fixing state management

Viral Tagdiwala
4 min readDec 23, 2022

Issues with the current meta

The frontend meta, as of writing this article revolves a lot around writing redundant state management logic coupled with the network calls. It’s the same cycle — call certain API, get the data, parse it and then push it over onto the client side state management framework.

A multitude of Libraries & a multitude of ways of doing things

This has become even more complicated with libraries like SWR and react-query where now you have two potential storage mechanisms to deal with — a client-side store that you (the developer) mutates, and a server-side store that also resides in the browser that the library mutates.

The current meta, as a result, incentivizes a lot of glue code to somehow have these two storage mechanisms in sync, which ideally shouldn’t be the case — why not have a single point of truth and have it all managed in one place?

Perfomance

There are also a whole lot of ways to go about client-side state management, and if for some reason you feel like sticking to what react provides out of the box, your performance takes a hit because of the number of re-renders your application might have to go through for each component that subscribes to a store.

A Proposed Architecture to fix this

The reason why I hesitate to call this fix the end-all-be-all is that there are certain design decisions that might not make it the best fit for every edge case out there, but certainly, for most traditional SPA’s this should work!

The architecture can roughly be divided into three parts — a client side handler, a server side handler and a Base Resource layer.

Client Side Handler

The client side handler takes care of the store mechanism — we use the useSyncExternalStore to generate a store and then expoliting generics in typescript, we handle CRUD over the store.

The reducers and actions are shaped in a way which allows generic CRUD to happen, however in order to provide this feature, a design decision had to be made to limit the use of this framework with backends having API responses that JSON API spec compliant. This comes in handy in many other places and is one of our major design decisions.

Server Side Handler

The server side handler is inspired by react-query where we take the ApiQueryParams and generate a hash string by deeply iterating the object. This hash string remains unique for each query sent out and we store the response in the localStorage. This comes in handy as our server side cache, preventing the server to be overloaded by a component that’s re-rendering unnecessarily making redundant calls to the server in the process.

Base Resource Layer

This is probably the meat and potatoes of the entire framework — again, by exploiting typescript generics, we take in a single interface that defines the object type that we are going to be working with and that’s it — this layer takes care of the rest.

Based on the calls you are performing (get, getAll, update, create, delete) it calls the cache service layer — that either chooses to turn around with locally cached values or move forward with a network call and return the base resource layer the necessary data.

From here, the resource layer calls the right action(s) and mutates the client side state while also giving back right data to the initator of the call.

What this means in practice

Say, you wanted to make a list view of Books —

Steps in a traditional react app using context API + react-query

  1. Setup interface for the book object type
  2. Setup actions
  3. Setup reducers
  4. Setup your store
  5. Call the API
  6. Ensure the right data is getting passed back
  7. Dispatch the recieved data from the API to the store
  8. Subscribe to the entire store, and display the results

Steps in the proposed architecture

  1. Setup interface for book object type
  2. Initilize the architecture by calling useOrchestrated and pass in your interface and API endpoint
  3. call .getAll()

…and that’s it — the orchestration mechanism take care of the rest. What’s better is the fact that you can subscribe to slices of the store as opposed to the entire store, giving strong performance improvements!

Improvements that are being worked upon

  1. A global level selector mechanism, like useSelectors that would allow components to subscribe to the state
  2. Adding logic over on the reducers to allow cases where more data is requested based on pagination, such data needs to be appended to the pre-existing store state

Final Thoughts

While I don’t believe the proposed framework is the magic bullet for all applications, it certainly can save a lot of dev time when it comes to applications having a lot of list views.

There are certainly many places of improvements that I’ve found over the course of developing this and am trying to resolve as I get more community feedback.

In the coming weeks, I’ll be adding detailed walk throughs of how each of these layers work internally, stay tuned!

A link to the repository — https://github.com/viral98/react-state-sync

--

--